mirror of
https://github.com/element-hq/element-web
synced 2024-11-27 03:36:07 +03:00
Merge branch 'develop' into widget_state_no_update_invitation_room
This commit is contained in:
commit
53a3f3861e
42 changed files with 1970 additions and 2732 deletions
|
@ -108,7 +108,7 @@ describe("Editing", () => {
|
|||
// send a load of padding events. We make them large, so that they fill the whole screen
|
||||
// and the client doesn't end up paginating into the event we want.
|
||||
let i = 0;
|
||||
while (i < 20) {
|
||||
while (i < 10) {
|
||||
await bob.sendMessage(room.room_id, mkPadding(i++));
|
||||
}
|
||||
|
||||
|
@ -127,7 +127,7 @@ describe("Editing", () => {
|
|||
cy.log(`Bot user sent edit event ${editEventId}`);
|
||||
|
||||
// ... then a load more padding ...
|
||||
while (i < 40) {
|
||||
while (i < 20) {
|
||||
await bob.sendMessage(room.room_id, mkPadding(i++));
|
||||
}
|
||||
});
|
||||
|
|
|
@ -16,8 +16,6 @@ limitations under the License.
|
|||
|
||||
/// <reference types="cypress" />
|
||||
|
||||
import { PollResponseEvent } from "matrix-events-sdk";
|
||||
|
||||
import { HomeserverInstance } from "../../plugins/utils/homeserver";
|
||||
import { MatrixClient } from "../../global";
|
||||
import Chainable = Cypress.Chainable;
|
||||
|
@ -70,8 +68,16 @@ describe("Polls", () => {
|
|||
cy.get('input[type="radio"]')
|
||||
.invoke("attr", "value")
|
||||
.then((optionId) => {
|
||||
const pollVote = PollResponseEvent.from([optionId], pollId).serialize();
|
||||
bot.sendEvent(roomId, pollVote.type, pollVote.content);
|
||||
// We can't use the js-sdk types for this stuff directly, so manually construct the event.
|
||||
bot.sendEvent(roomId, "org.matrix.msc3381.poll.response", {
|
||||
"m.relates_to": {
|
||||
rel_type: "m.reference",
|
||||
event_id: pollId,
|
||||
},
|
||||
"org.matrix.msc3381.poll.response": {
|
||||
answers: [optionId],
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
|
|
@ -49,6 +49,10 @@ limitations under the License.
|
|||
mask-image: url("$(res)/img/element-icons/security.svg");
|
||||
}
|
||||
|
||||
.mx_UserSettingsDialog_sessionsIcon::before {
|
||||
mask-image: url("$(res)/img/element-icons/settings/devices.svg");
|
||||
}
|
||||
|
||||
.mx_UserSettingsDialog_helpIcon::before {
|
||||
mask-image: url("$(res)/img/element-icons/settings/help.svg");
|
||||
}
|
||||
|
|
|
@ -619,6 +619,17 @@ $left-gutter: 64px;
|
|||
ul ol {
|
||||
list-style-type: revert;
|
||||
}
|
||||
|
||||
/* Make list type disc to match rich text editor */
|
||||
> ul {
|
||||
list-style-type: disc;
|
||||
}
|
||||
|
||||
/* Remove top and bottom margin for better consecutive list display */
|
||||
> :is(ol, ul) {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@ limitations under the License.
|
|||
}
|
||||
|
||||
.mx_WysiwygComposer_Editor_content {
|
||||
line-height: $font-22px;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
outline: none;
|
||||
|
@ -35,6 +36,19 @@ limitations under the License.
|
|||
.caretNode {
|
||||
user-select: all;
|
||||
}
|
||||
|
||||
ul,
|
||||
ol {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
padding-inline-start: $spacing-28;
|
||||
}
|
||||
|
||||
// model output always includes a linebreak but we do not want the user
|
||||
// to see it when writing input in lists
|
||||
:is(ol, ul) + br:last-of-type {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_WysiwygComposer_Editor_content_placeholder::before {
|
||||
|
|
3
res/img/element-icons/room/composer/bulleted_list.svg
Normal file
3
res/img/element-icons/room/composer/bulleted_list.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg width="13" height="10" viewBox="0 0 13 10" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1.66666 4C1.11332 4 0.666656 4.44667 0.666656 5C0.666656 5.55333 1.11332 6 1.66666 6C2.21999 6 2.66666 5.55333 2.66666 5C2.66666 4.44667 2.21999 4 1.66666 4ZM1.66666 0C1.11332 0 0.666656 0.446667 0.666656 1C0.666656 1.55333 1.11332 2 1.66666 2C2.21999 2 2.66666 1.55333 2.66666 1C2.66666 0.446667 2.21999 0 1.66666 0ZM1.66666 8C1.11332 8 0.666656 8.45333 0.666656 9C0.666656 9.54667 1.11999 10 1.66666 10C2.21332 10 2.66666 9.54667 2.66666 9C2.66666 8.45333 2.21999 8 1.66666 8ZM4.33332 9.66667H12.3333C12.7 9.66667 13 9.36667 13 9C13 8.63333 12.7 8.33333 12.3333 8.33333H4.33332C3.96666 8.33333 3.66666 8.63333 3.66666 9C3.66666 9.36667 3.96666 9.66667 4.33332 9.66667ZM4.33332 5.66667H12.3333C12.7 5.66667 13 5.36667 13 5C13 4.63333 12.7 4.33333 12.3333 4.33333H4.33332C3.96666 4.33333 3.66666 4.63333 3.66666 5C3.66666 5.36667 3.96666 5.66667 4.33332 5.66667ZM3.66666 1C3.66666 1.36667 3.96666 1.66667 4.33332 1.66667H12.3333C12.7 1.66667 13 1.36667 13 1C13 0.633333 12.7 0.333333 12.3333 0.333333H4.33332C3.96666 0.333333 3.66666 0.633333 3.66666 1Z" fill="currentColor"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.2 KiB |
3
res/img/element-icons/room/composer/numbered_list.svg
Normal file
3
res/img/element-icons/room/composer/numbered_list.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg width="13" height="12" viewBox="0 0 13 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4.33334 2.66663H12.3333C12.7 2.66663 13 2.36663 13 1.99996C13 1.63329 12.7 1.33329 12.3333 1.33329H4.33334C3.96668 1.33329 3.66668 1.63329 3.66668 1.99996C3.66668 2.36663 3.96668 2.66663 4.33334 2.66663ZM12.3333 9.33329H4.33334C3.96668 9.33329 3.66668 9.63329 3.66668 9.99996C3.66668 10.3666 3.96668 10.6666 4.33334 10.6666H12.3333C12.7 10.6666 13 10.3666 13 9.99996C13 9.63329 12.7 9.33329 12.3333 9.33329ZM12.3333 5.33329H4.33334C3.96668 5.33329 3.66668 5.63329 3.66668 5.99996C3.66668 6.36663 3.96668 6.66663 4.33334 6.66663H12.3333C12.7 6.66663 13 6.36663 13 5.99996C13 5.63329 12.7 5.33329 12.3333 5.33329ZM2.00001 8.66663H0.666677C0.48001 8.66663 0.333344 8.81329 0.333344 8.99996C0.333344 9.18663 0.48001 9.33329 0.666677 9.33329H1.66668V9.66663H1.33334C1.14668 9.66663 1.00001 9.81329 1.00001 9.99996C1.00001 10.1866 1.14668 10.3333 1.33334 10.3333H1.66668V10.6666H0.666677C0.48001 10.6666 0.333344 10.8133 0.333344 11C0.333344 11.1866 0.48001 11.3333 0.666677 11.3333H2.00001C2.18668 11.3333 2.33334 11.1866 2.33334 11V8.99996C2.33334 8.81329 2.18668 8.66663 2.00001 8.66663ZM0.666677 1.33329H1.00001V2.99996C1.00001 3.18663 1.14668 3.33329 1.33334 3.33329C1.52001 3.33329 1.66668 3.18663 1.66668 2.99996V0.999959C1.66668 0.813293 1.52001 0.666626 1.33334 0.666626H0.666677C0.48001 0.666626 0.333344 0.813293 0.333344 0.999959C0.333344 1.18663 0.48001 1.33329 0.666677 1.33329ZM2.00001 4.66663H0.666677C0.48001 4.66663 0.333344 4.81329 0.333344 4.99996C0.333344 5.18663 0.48001 5.33329 0.666677 5.33329H1.53334L0.413343 6.63996C0.36001 6.69996 0.333344 6.77996 0.333344 6.85329V6.99996C0.333344 7.18663 0.48001 7.33329 0.666677 7.33329H2.00001C2.18668 7.33329 2.33334 7.18663 2.33334 6.99996C2.33334 6.81329 2.18668 6.66663 2.00001 6.66663H1.13334L2.25334 5.35996C2.30668 5.29996 2.33334 5.21996 2.33334 5.14663V4.99996C2.33334 4.81329 2.18668 4.66663 2.00001 4.66663Z" fill="currentColor"/>
|
||||
</svg>
|
After Width: | Height: | Size: 2 KiB |
5
res/img/element-icons/settings/devices.svg
Normal file
5
res/img/element-icons/settings/devices.svg
Normal file
|
@ -0,0 +1,5 @@
|
|||
<svg width="21" height="19" viewBox="0 0 21 19" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M20 2V7.005L18 7.003V4H2V14H13V16H2C1.45 16 0.979333 15.8043 0.588 15.413C0.196 15.021 0 14.55 0 14V2C0 1.45 0.196 0.979333 0.588 0.588C0.979333 0.196 1.45 0 2 0H18C18.55 0 19.021 0.196 19.413 0.588C19.8043 0.979333 20 1.45 20 2Z" fill="currentColor"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M15 8L20 8.005C20.55 8.005 21 8.45 21 9V18C21 18.55 20.55 19 20
|
||||
19H15C14.45 19 14 18.55 14 18V9C14 8.45 14.45 8 15 8ZM15 17H20V10H15V17Z" fill="currentColor"/>
|
||||
</svg>
|
After Width: | Height: | Size: 574 B |
|
@ -20,7 +20,8 @@ import { logger } from "matrix-js-sdk/src/logger";
|
|||
import { removeDirectionOverrideChars } from "matrix-js-sdk/src/utils";
|
||||
import { GuestAccess, HistoryVisibility, JoinRule } from "matrix-js-sdk/src/@types/partials";
|
||||
import { EventType, MsgType } from "matrix-js-sdk/src/@types/event";
|
||||
import { M_POLL_START, M_POLL_END, PollStartEvent } from "matrix-events-sdk";
|
||||
import { M_POLL_START, M_POLL_END } from "matrix-js-sdk/src/@types/polls";
|
||||
import { PollStartEvent } from "matrix-js-sdk/src/extensible_events_v1/PollStartEvent";
|
||||
|
||||
import { _t } from "./languageHandler";
|
||||
import * as Roles from "./Roles";
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
/*
|
||||
Copyright 2015, 2016 OpenMarket Ltd
|
||||
Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
|
||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||
Copyright 2015, 2016, 2019, 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.
|
||||
|
@ -77,9 +76,13 @@ export default class ViewSource extends React.Component<IProps, IState> {
|
|||
<summary>
|
||||
<span className="mx_ViewSource_heading">{_t("Decrypted event source")}</span>
|
||||
</summary>
|
||||
{decryptedEventSource ? (
|
||||
<CopyableText getTextToCopy={copyDecryptedFunc}>
|
||||
<SyntaxHighlight language="json">{stringify(decryptedEventSource)}</SyntaxHighlight>
|
||||
</CopyableText>
|
||||
) : (
|
||||
<div>{_t("Decrypted source unavailable")}</div>
|
||||
)}
|
||||
</details>
|
||||
<details className="mx_ViewSource_details">
|
||||
<summary>
|
||||
|
|
|
@ -21,7 +21,7 @@ import { EventStatus, MatrixEvent } from "matrix-js-sdk/src/models/event";
|
|||
import { EventType, RelationType } from "matrix-js-sdk/src/@types/event";
|
||||
import { Relations } from "matrix-js-sdk/src/models/relations";
|
||||
import { RoomMemberEvent } from "matrix-js-sdk/src/models/room-member";
|
||||
import { M_POLL_START } from "matrix-events-sdk";
|
||||
import { M_POLL_START } from "matrix-js-sdk/src/@types/polls";
|
||||
import { Thread } from "matrix-js-sdk/src/models/thread";
|
||||
|
||||
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||
|
|
|
@ -17,7 +17,7 @@ limitations under the License.
|
|||
import React from "react";
|
||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||
import { PollEndEvent } from "matrix-events-sdk";
|
||||
import { PollEndEvent } from "matrix-js-sdk/src/extensible_events_v1/PollEndEvent";
|
||||
|
||||
import { _t } from "../../../languageHandler";
|
||||
import { IDialogProps } from "./IDialogProps";
|
||||
|
|
|
@ -164,7 +164,7 @@ export default class UserSettingsDialog extends React.Component<IProps, IState>
|
|||
new Tab(
|
||||
UserTab.SessionManager,
|
||||
_td("Sessions"),
|
||||
"mx_UserSettingsDialog_securityIcon",
|
||||
"mx_UserSettingsDialog_sessionsIcon",
|
||||
<SessionManagerTab />,
|
||||
// don't track with posthog while under construction
|
||||
undefined,
|
||||
|
|
|
@ -17,14 +17,14 @@ limitations under the License.
|
|||
import React, { ChangeEvent, createRef } from "react";
|
||||
import { Room } from "matrix-js-sdk/src/models/room";
|
||||
import {
|
||||
IPartialEvent,
|
||||
KNOWN_POLL_KIND,
|
||||
KnownPollKind,
|
||||
M_POLL_KIND_DISCLOSED,
|
||||
M_POLL_KIND_UNDISCLOSED,
|
||||
M_POLL_START,
|
||||
PollStartEvent,
|
||||
} from "matrix-events-sdk";
|
||||
} from "matrix-js-sdk/src/@types/polls";
|
||||
import { PollStartEvent } from "matrix-js-sdk/src/extensible_events_v1/PollStartEvent";
|
||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||
import { IPartialEvent } from "matrix-js-sdk/src/@types/extensible_events";
|
||||
|
||||
import ScrollableBaseModal, { IScrollableBaseState } from "../dialogs/ScrollableBaseModal";
|
||||
import { IDialogProps } from "../dialogs/IDialogProps";
|
||||
|
@ -51,7 +51,7 @@ interface IState extends IScrollableBaseState {
|
|||
question: string;
|
||||
options: string[];
|
||||
busy: boolean;
|
||||
kind: KNOWN_POLL_KIND;
|
||||
kind: KnownPollKind;
|
||||
autoFocusTarget: FocusTarget;
|
||||
}
|
||||
|
||||
|
@ -263,7 +263,7 @@ export default class PollCreateDialog extends ScrollableBaseModal<IProps, IState
|
|||
};
|
||||
}
|
||||
|
||||
function pollTypeNotes(kind: KNOWN_POLL_KIND): string {
|
||||
function pollTypeNotes(kind: KnownPollKind): string {
|
||||
if (M_POLL_KIND_DISCLOSED.matches(kind.name)) {
|
||||
return _t("Voters see results as soon as they have voted");
|
||||
} else {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
Copyright 2017 Michael Telatynski <7t3chguy@gmail.com>
|
||||
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.
|
||||
|
@ -25,7 +26,7 @@ interface IProps {
|
|||
export default class SyntaxHighlight extends React.PureComponent<IProps> {
|
||||
public render(): JSX.Element {
|
||||
const { children: content, language } = this.props;
|
||||
const highlighted = language ? hljs.highlight(language, content) : hljs.highlightAuto(content);
|
||||
const highlighted = language ? hljs.highlight(content, { language }) : hljs.highlightAuto(content);
|
||||
|
||||
return (
|
||||
<pre className={`mx_SyntaxHighlight hljs language-${highlighted.language}`}>
|
||||
|
|
|
@ -20,17 +20,11 @@ import { logger } from "matrix-js-sdk/src/logger";
|
|||
import { MatrixEvent, MatrixEventEvent } from "matrix-js-sdk/src/models/event";
|
||||
import { Relations, RelationsEvent } from "matrix-js-sdk/src/models/relations";
|
||||
import { MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||
import {
|
||||
M_POLL_END,
|
||||
M_POLL_KIND_DISCLOSED,
|
||||
M_POLL_RESPONSE,
|
||||
M_POLL_START,
|
||||
NamespacedValue,
|
||||
PollAnswerSubevent,
|
||||
PollResponseEvent,
|
||||
PollStartEvent,
|
||||
} from "matrix-events-sdk";
|
||||
import { M_POLL_END, M_POLL_KIND_DISCLOSED, M_POLL_RESPONSE, M_POLL_START } from "matrix-js-sdk/src/@types/polls";
|
||||
import { RelatedRelations } from "matrix-js-sdk/src/models/related-relations";
|
||||
import { NamespacedValue } from "matrix-events-sdk";
|
||||
import { PollStartEvent, PollAnswerSubevent } from "matrix-js-sdk/src/extensible_events_v1/PollStartEvent";
|
||||
import { PollResponseEvent } from "matrix-js-sdk/src/extensible_events_v1/PollResponseEvent";
|
||||
|
||||
import { _t } from "../../../languageHandler";
|
||||
import Modal from "../../../Modal";
|
||||
|
@ -454,7 +448,7 @@ export default class MPollBody extends React.Component<IBodyProps, IState> {
|
|||
|
||||
return (
|
||||
<div className="mx_MPollBody">
|
||||
<h2>
|
||||
<h2 data-testid="pollQuestion">
|
||||
{poll.question.text}
|
||||
{editedSpan}
|
||||
</h2>
|
||||
|
@ -477,7 +471,12 @@ export default class MPollBody extends React.Component<IBodyProps, IState> {
|
|||
|
||||
const answerPercent = totalVotes === 0 ? 0 : Math.round((100.0 * answerVotes) / totalVotes);
|
||||
return (
|
||||
<div key={answer.id} className={cls} onClick={() => this.selectOption(answer.id)}>
|
||||
<div
|
||||
data-testid={`pollOption-${answer.id}`}
|
||||
key={answer.id}
|
||||
className={cls}
|
||||
onClick={() => this.selectOption(answer.id)}
|
||||
>
|
||||
{ended ? (
|
||||
<EndedPollOption answer={answer} checked={checked} votesText={votesText} />
|
||||
) : (
|
||||
|
@ -499,7 +498,9 @@ export default class MPollBody extends React.Component<IBodyProps, IState> {
|
|||
);
|
||||
})}
|
||||
</div>
|
||||
<div className="mx_MPollBody_totalVotes">{totalText}</div>
|
||||
<div data-testid="totalVotes" className="mx_MPollBody_totalVotes">
|
||||
{totalText}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ import React, { createRef } from "react";
|
|||
import { EventType, MsgType } from "matrix-js-sdk/src/@types/event";
|
||||
import { M_BEACON_INFO } from "matrix-js-sdk/src/@types/beacon";
|
||||
import { M_LOCATION } from "matrix-js-sdk/src/@types/location";
|
||||
import { M_POLL_START } from "matrix-events-sdk";
|
||||
import { M_POLL_START } from "matrix-js-sdk/src/@types/polls";
|
||||
import { MatrixEventEvent } from "matrix-js-sdk/src/models/event";
|
||||
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
|
|
|
@ -16,7 +16,7 @@ limitations under the License.
|
|||
|
||||
import classNames from "classnames";
|
||||
import { IEventRelation } from "matrix-js-sdk/src/models/event";
|
||||
import { M_POLL_START } from "matrix-events-sdk";
|
||||
import { M_POLL_START } from "matrix-js-sdk/src/@types/polls";
|
||||
import React, { createContext, MouseEventHandler, ReactElement, useContext, useRef } from "react";
|
||||
import { Room } from "matrix-js-sdk/src/models/room";
|
||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||
|
|
|
@ -20,7 +20,7 @@ import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
|||
import { Relations } from "matrix-js-sdk/src/models/relations";
|
||||
import { EventType, RelationType } from "matrix-js-sdk/src/@types/event";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { M_POLL_START, M_POLL_RESPONSE, M_POLL_END } from "matrix-events-sdk";
|
||||
import { M_POLL_START, M_POLL_RESPONSE, M_POLL_END } from "matrix-js-sdk/src/@types/polls";
|
||||
|
||||
import dis from "../../../dispatcher/dispatcher";
|
||||
import { Action } from "../../../dispatcher/actions";
|
||||
|
|
|
@ -20,7 +20,7 @@ import React, { CSSProperties, forwardRef, memo, MutableRefObject, ReactNode } f
|
|||
import { useIsExpanded } from "../hooks/useIsExpanded";
|
||||
import { useSelection } from "../hooks/useSelection";
|
||||
|
||||
const HEIGHT_BREAKING_POINT = 20;
|
||||
const HEIGHT_BREAKING_POINT = 24;
|
||||
|
||||
interface EditorProps {
|
||||
disabled: boolean;
|
||||
|
|
|
@ -24,6 +24,8 @@ import { Icon as UnderlineIcon } from "../../../../../../res/img/element-icons/r
|
|||
import { Icon as StrikeThroughIcon } from "../../../../../../res/img/element-icons/room/composer/strikethrough.svg";
|
||||
import { Icon as InlineCodeIcon } from "../../../../../../res/img/element-icons/room/composer/inline_code.svg";
|
||||
import { Icon as LinkIcon } from "../../../../../../res/img/element-icons/room/composer/link.svg";
|
||||
import { Icon as BulletedListIcon } from "../../../../../../res/img/element-icons/room/composer/bulleted_list.svg";
|
||||
import { Icon as NumberedListIcon } from "../../../../../../res/img/element-icons/room/composer/numbered_list.svg";
|
||||
import AccessibleTooltipButton from "../../../elements/AccessibleTooltipButton";
|
||||
import { Alignment } from "../../../elements/Tooltip";
|
||||
import { KeyboardShortcut } from "../../../settings/KeyboardShortcut";
|
||||
|
@ -109,6 +111,18 @@ export function FormattingButtons({ composer, actionStates }: FormattingButtonsP
|
|||
onClick={() => composer.strikeThrough()}
|
||||
icon={<StrikeThroughIcon className="mx_FormattingButtons_Icon" />}
|
||||
/>
|
||||
<Button
|
||||
isActive={actionStates.unorderedList === "reversed"}
|
||||
label={_td("Bulleted list")}
|
||||
onClick={() => composer.unorderedList()}
|
||||
icon={<BulletedListIcon className="mx_FormattingButtons_Icon" />}
|
||||
/>
|
||||
<Button
|
||||
isActive={actionStates.orderedList === "reversed"}
|
||||
label={_td("Numbered list")}
|
||||
onClick={() => composer.orderedList()}
|
||||
icon={<NumberedListIcon className="mx_FormattingButtons_Icon" />}
|
||||
/>
|
||||
<Button
|
||||
isActive={actionStates.inlineCode === "reversed"}
|
||||
label={_td("Code")}
|
||||
|
|
|
@ -353,8 +353,6 @@ export default class SecurityUserSettingsTab extends React.Component<IProps, ISt
|
|||
{PosthogAnalytics.instance.isEnabled() && (
|
||||
<SettingsFlag name="pseudonymousAnalyticsOptIn" level={SettingLevel.ACCOUNT} />
|
||||
)}
|
||||
</div>
|
||||
<div className="mx_SettingsTab_section">
|
||||
<span className="mx_SettingsTab_subheading">{_t("Sessions")}</span>
|
||||
<SettingsFlag name="deviceClientInformationOptIn" level={SettingLevel.ACCOUNT} />
|
||||
</div>
|
||||
|
|
|
@ -17,7 +17,8 @@ limitations under the License.
|
|||
import React from "react";
|
||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||
import { EventType, MsgType, RelationType } from "matrix-js-sdk/src/@types/event";
|
||||
import { M_POLL_START, Optional } from "matrix-events-sdk";
|
||||
import { Optional } from "matrix-events-sdk";
|
||||
import { M_POLL_START } from "matrix-js-sdk/src/@types/polls";
|
||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||
import { GroupCallIntent } from "matrix-js-sdk/src/webrtc/groupCall";
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { M_POLL_START } from "matrix-events-sdk";
|
||||
import { M_POLL_START } from "matrix-js-sdk/src/@types/polls";
|
||||
import { M_BEACON_INFO } from "matrix-js-sdk/src/@types/beacon";
|
||||
import { MatrixEvent, MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
|
|
|
@ -2135,6 +2135,8 @@
|
|||
"Stop recording": "Stop recording",
|
||||
"Italic": "Italic",
|
||||
"Underline": "Underline",
|
||||
"Bulleted list": "Bulleted list",
|
||||
"Numbered list": "Numbered list",
|
||||
"Code": "Code",
|
||||
"Link": "Link",
|
||||
"Edit link": "Edit link",
|
||||
|
@ -3450,6 +3452,7 @@
|
|||
"User menu": "User menu",
|
||||
"Could not load user profile": "Could not load user profile",
|
||||
"Decrypted event source": "Decrypted event source",
|
||||
"Decrypted source unavailable": "Decrypted source unavailable",
|
||||
"Original event source": "Original event source",
|
||||
"Event ID: %(eventId)s": "Event ID: %(eventId)s",
|
||||
"Thread root ID: %(threadRootId)s": "Thread root ID: %(threadRootId)s",
|
||||
|
|
|
@ -17,7 +17,7 @@ limitations under the License.
|
|||
import { Room } from "matrix-js-sdk/src/models/room";
|
||||
import { isNullOrUndefined } from "matrix-js-sdk/src/utils";
|
||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||
import { M_POLL_START } from "matrix-events-sdk";
|
||||
import { M_POLL_START } from "matrix-js-sdk/src/@types/polls";
|
||||
|
||||
import { ActionPayload } from "../../dispatcher/payloads";
|
||||
import { AsyncStoreWithClient } from "../AsyncStoreWithClient";
|
||||
|
|
|
@ -15,7 +15,9 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||
import { InvalidEventError, M_POLL_START_EVENT_CONTENT, PollStartEvent } from "matrix-events-sdk";
|
||||
import { PollStartEventContent } from "matrix-js-sdk/src/@types/polls";
|
||||
import { InvalidEventError } from "matrix-js-sdk/src/extensible_events_v1/InvalidEventError";
|
||||
import { PollStartEvent } from "matrix-js-sdk/src/extensible_events_v1/PollStartEvent";
|
||||
|
||||
import { IPreview } from "./IPreview";
|
||||
import { TagID } from "../models";
|
||||
|
@ -43,7 +45,7 @@ export class PollStartEventPreview implements IPreview {
|
|||
try {
|
||||
const poll = new PollStartEvent({
|
||||
type: event.getType(),
|
||||
content: eventContent as M_POLL_START_EVENT_CONTENT,
|
||||
content: eventContent as PollStartEventContent,
|
||||
});
|
||||
|
||||
let question = poll.question.text.trim();
|
||||
|
|
|
@ -16,7 +16,7 @@ limitations under the License.
|
|||
|
||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||
import { EventType, MsgType } from "matrix-js-sdk/src/@types/event";
|
||||
import { M_POLL_START } from "matrix-events-sdk";
|
||||
import { M_POLL_START } from "matrix-js-sdk/src/@types/polls";
|
||||
import { M_BEACON_INFO } from "matrix-js-sdk/src/@types/beacon";
|
||||
import { IContent } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ import { EventStatus, MatrixEvent } from "matrix-js-sdk/src/models/event";
|
|||
import { EventType, EVENT_VISIBILITY_CHANGE_TYPE, MsgType, RelationType } from "matrix-js-sdk/src/@types/event";
|
||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { M_POLL_START } from "matrix-events-sdk";
|
||||
import { M_POLL_START } from "matrix-js-sdk/src/@types/polls";
|
||||
import { M_LOCATION } from "matrix-js-sdk/src/@types/location";
|
||||
import { M_BEACON_INFO } from "matrix-js-sdk/src/@types/beacon";
|
||||
import { THREAD_RELATION_TYPE } from "matrix-js-sdk/src/models/thread";
|
||||
|
|
|
@ -16,7 +16,7 @@ limitations under the License.
|
|||
|
||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||
import { EventType } from "matrix-js-sdk/src/@types/event";
|
||||
import { M_POLL_START } from "matrix-events-sdk";
|
||||
import { M_POLL_START } from "matrix-js-sdk/src/@types/polls";
|
||||
|
||||
export default class PinningUtils {
|
||||
/**
|
||||
|
|
59
test/components/structures/ViewSource-test.tsx
Normal file
59
test/components/structures/ViewSource-test.tsx
Normal file
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
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 { render } from "@testing-library/react";
|
||||
import { EventType, MatrixEvent } from "matrix-js-sdk/src/matrix";
|
||||
import React from "react";
|
||||
|
||||
import ViewSource from "../../../src/components/structures/ViewSource";
|
||||
import { mkEvent, stubClient } from "../../test-utils/test-utils";
|
||||
|
||||
describe("ThreadView", () => {
|
||||
const ROOM_ID = "!roomId:example.org";
|
||||
const SENDER = "@alice:example.org";
|
||||
|
||||
let messageEvent: MatrixEvent;
|
||||
|
||||
const redactionEvent = mkEvent({
|
||||
user: SENDER,
|
||||
event: true,
|
||||
type: EventType.RoomRedaction,
|
||||
content: {},
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
messageEvent = new MatrixEvent({
|
||||
type: EventType.RoomMessageEncrypted,
|
||||
room_id: ROOM_ID,
|
||||
sender: SENDER,
|
||||
content: {},
|
||||
state_key: undefined,
|
||||
});
|
||||
messageEvent.makeRedacted(redactionEvent);
|
||||
});
|
||||
|
||||
beforeEach(stubClient);
|
||||
|
||||
// See https://github.com/vector-im/element-web/issues/24165
|
||||
it("doesn't error when viewing redacted encrypted messages", () => {
|
||||
// Sanity checks
|
||||
expect(messageEvent.isEncrypted()).toBeTruthy();
|
||||
// @ts-ignore clearEvent is private, but it's being used directly <ViewSource />
|
||||
expect(messageEvent.clearEvent).toBe(undefined);
|
||||
|
||||
expect(() => render(<ViewSource mxEvent={messageEvent} onFinished={() => {}} />)).not.toThrow();
|
||||
});
|
||||
});
|
|
@ -26,7 +26,8 @@ import {
|
|||
getBeaconInfoIdentifier,
|
||||
EventType,
|
||||
} from "matrix-js-sdk/src/matrix";
|
||||
import { M_POLL_KIND_DISCLOSED, PollStartEvent } from "matrix-events-sdk";
|
||||
import { M_POLL_KIND_DISCLOSED } from "matrix-js-sdk/src/@types/polls";
|
||||
import { PollStartEvent } from "matrix-js-sdk/src/extensible_events_v1/PollStartEvent";
|
||||
import { FeatureSupport, Thread } from "matrix-js-sdk/src/models/thread";
|
||||
import { mocked } from "jest-mock";
|
||||
import { act } from "@testing-library/react";
|
||||
|
|
|
@ -18,7 +18,7 @@ import React from "react";
|
|||
import { act } from "react-dom/test-utils";
|
||||
import { MatrixEvent, EventType } from "matrix-js-sdk/src/matrix";
|
||||
import { LocationAssetType, M_ASSET, M_LOCATION, M_TIMESTAMP } from "matrix-js-sdk/src/@types/location";
|
||||
import { TEXT_NODE_TYPE } 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 userEvent from "@testing-library/user-event";
|
||||
|
||||
|
@ -248,7 +248,7 @@ describe("ForwardDialog", () => {
|
|||
const expectedStrippedContent = {
|
||||
...modernLocationEvent.getContent(),
|
||||
body: text,
|
||||
[TEXT_NODE_TYPE.name]: text,
|
||||
[M_TEXT.name]: text,
|
||||
[M_TIMESTAMP.name]: now,
|
||||
[M_ASSET.name]: { type: LocationAssetType.Pin },
|
||||
[M_LOCATION.name]: {
|
||||
|
@ -276,7 +276,7 @@ describe("ForwardDialog", () => {
|
|||
const expectedStrippedContent = {
|
||||
...modernLocationEvent.getContent(),
|
||||
body: text,
|
||||
[TEXT_NODE_TYPE.name]: text,
|
||||
[M_TEXT.name]: text,
|
||||
[M_ASSET.name]: { type: LocationAssetType.Pin },
|
||||
[M_LOCATION.name]: {
|
||||
uri: geoUri,
|
||||
|
@ -297,7 +297,7 @@ describe("ForwardDialog", () => {
|
|||
const expectedContent = {
|
||||
msgtype: "m.location",
|
||||
body: text,
|
||||
[TEXT_NODE_TYPE.name]: text,
|
||||
[M_TEXT.name]: text,
|
||||
[M_ASSET.name]: { type: LocationAssetType.Pin },
|
||||
[M_LOCATION.name]: {
|
||||
uri: geoUri,
|
||||
|
|
|
@ -18,14 +18,10 @@ import React from "react";
|
|||
// eslint-disable-next-line deprecate/import
|
||||
import { mount, ReactWrapper } from "enzyme";
|
||||
import { Room } from "matrix-js-sdk/src/models/room";
|
||||
import {
|
||||
M_POLL_KIND_DISCLOSED,
|
||||
M_POLL_KIND_UNDISCLOSED,
|
||||
M_POLL_START,
|
||||
M_TEXT,
|
||||
PollStartEvent,
|
||||
} from "matrix-events-sdk";
|
||||
import { M_POLL_KIND_DISCLOSED, M_POLL_KIND_UNDISCLOSED, M_POLL_START } from "matrix-js-sdk/src/@types/polls";
|
||||
import { PollStartEvent } from "matrix-js-sdk/src/extensible_events_v1/PollStartEvent";
|
||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||
import { M_TEXT } from "matrix-js-sdk/src/@types/extensible_events";
|
||||
|
||||
import { findById, getMockClientWithEventEmitter } from "../../../test-utils";
|
||||
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
|
||||
|
|
38
test/components/views/elements/SyntaxHighlight-test.tsx
Normal file
38
test/components/views/elements/SyntaxHighlight-test.tsx
Normal file
|
@ -0,0 +1,38 @@
|
|||
/* eslint @typescript-eslint/no-unused-vars: ["error", { "varsIgnorePattern": "^_" }] */
|
||||
/*
|
||||
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 { render } from "@testing-library/react";
|
||||
import hljs, { type HighlightOptions } from "highlight.js";
|
||||
import React from "react";
|
||||
|
||||
import SyntaxHighlight from "../../../../src/components/views/elements/SyntaxHighlight";
|
||||
|
||||
describe("<SyntaxHighlight />", () => {
|
||||
it("renders", () => {
|
||||
const { container } = render(<SyntaxHighlight>console.log("Hello, World!");</SyntaxHighlight>);
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it.each(["json", "javascript", "css"])("uses the provided language", (lang) => {
|
||||
const mock = jest.spyOn(hljs, "highlight");
|
||||
|
||||
render(<SyntaxHighlight language={lang}>// Hello, World</SyntaxHighlight>);
|
||||
|
||||
const [_lang, opts] = mock.mock.lastCall!;
|
||||
expect((opts as HighlightOptions)["language"]).toBe(lang);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,30 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<SyntaxHighlight /> renders 1`] = `
|
||||
<div>
|
||||
<pre
|
||||
class="mx_SyntaxHighlight hljs language-arcade"
|
||||
>
|
||||
<code>
|
||||
<span
|
||||
class="hljs-built_in"
|
||||
>
|
||||
console
|
||||
</span>
|
||||
.
|
||||
<span
|
||||
class="hljs-built_in"
|
||||
>
|
||||
log
|
||||
</span>
|
||||
(
|
||||
<span
|
||||
class="hljs-string"
|
||||
>
|
||||
"Hello, World!"
|
||||
</span>
|
||||
);
|
||||
</code>
|
||||
</pre>
|
||||
</div>
|
||||
`;
|
|
@ -15,8 +15,7 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React from "react";
|
||||
// eslint-disable-next-line deprecate/import
|
||||
import { mount, ReactWrapper } from "enzyme";
|
||||
import { fireEvent, render, RenderResult } from "@testing-library/react";
|
||||
import { MatrixClient, MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
|
||||
import { Relations } from "matrix-js-sdk/src/models/relations";
|
||||
import { RelatedRelations } from "matrix-js-sdk/src/models/related-relations";
|
||||
|
@ -26,10 +25,10 @@ import {
|
|||
M_POLL_KIND_UNDISCLOSED,
|
||||
M_POLL_RESPONSE,
|
||||
M_POLL_START,
|
||||
M_POLL_START_EVENT_CONTENT,
|
||||
M_TEXT,
|
||||
POLL_ANSWER,
|
||||
} from "matrix-events-sdk";
|
||||
PollStartEventContent,
|
||||
PollAnswer,
|
||||
} from "matrix-js-sdk/src/@types/polls";
|
||||
import { M_TEXT } from "matrix-js-sdk/src/@types/extensible_events";
|
||||
import { MockedObject } from "jest-mock";
|
||||
|
||||
import {
|
||||
|
@ -44,6 +43,8 @@ import { IBodyProps } from "../../../../src/components/views/messages/IBodyProps
|
|||
import { getMockClientWithEventEmitter } from "../../../test-utils";
|
||||
import MatrixClientContext from "../../../../src/contexts/MatrixClientContext";
|
||||
import MPollBody from "../../../../src/components/views/messages/MPollBody";
|
||||
import { RoomPermalinkCreator } from "../../../../src/utils/permalinks/Permalinks";
|
||||
import { MediaEventHelper } from "../../../../src/utils/MediaEventHelper";
|
||||
|
||||
const CHECKED = "mx_MPollBody_option_checked";
|
||||
|
||||
|
@ -85,13 +86,13 @@ describe("MPollBody", () => {
|
|||
new RelatedRelations([newEndRelations([])]),
|
||||
),
|
||||
).toEqual([
|
||||
new UserVote(ev1.getTs(), ev1.getSender(), ev1.getContent()[M_POLL_RESPONSE.name].answers),
|
||||
new UserVote(ev1.getTs(), ev1.getSender()!, ev1.getContent()[M_POLL_RESPONSE.name].answers),
|
||||
new UserVote(
|
||||
badEvent.getTs(),
|
||||
badEvent.getSender(),
|
||||
badEvent.getSender()!,
|
||||
[], // should be spoiled
|
||||
),
|
||||
new UserVote(ev2.getTs(), ev2.getSender(), ev2.getContent()[M_POLL_RESPONSE.name].answers),
|
||||
new UserVote(ev2.getTs(), ev2.getSender()!, ev2.getContent()[M_POLL_RESPONSE.name].answers),
|
||||
]);
|
||||
});
|
||||
|
||||
|
@ -146,14 +147,14 @@ describe("MPollBody", () => {
|
|||
});
|
||||
|
||||
it("renders no votes if none were made", () => {
|
||||
const votes = [];
|
||||
const body = newMPollBody(votes);
|
||||
expect(votesCount(body, "pizza")).toBe("");
|
||||
expect(votesCount(body, "poutine")).toBe("");
|
||||
expect(votesCount(body, "italian")).toBe("");
|
||||
expect(votesCount(body, "wings")).toBe("");
|
||||
expect(body.find(".mx_MPollBody_totalVotes").text()).toBe("No votes cast");
|
||||
expect(body.find("h2").html()).toEqual("<h2>What should we order for the party?</h2>");
|
||||
const votes: MatrixEvent[] = [];
|
||||
const renderResult = newMPollBody(votes);
|
||||
expect(votesCount(renderResult, "pizza")).toBe("");
|
||||
expect(votesCount(renderResult, "poutine")).toBe("");
|
||||
expect(votesCount(renderResult, "italian")).toBe("");
|
||||
expect(votesCount(renderResult, "wings")).toBe("");
|
||||
expect(renderResult.getByTestId("totalVotes").innerHTML).toBe("No votes cast");
|
||||
expect(renderResult.getByText("What should we order for the party?")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("finds votes from multiple people", () => {
|
||||
|
@ -163,12 +164,12 @@ describe("MPollBody", () => {
|
|||
responseEvent("@catrd:example.com", "poutine"),
|
||||
responseEvent("@dune2:example.com", "wings"),
|
||||
];
|
||||
const body = newMPollBody(votes);
|
||||
expect(votesCount(body, "pizza")).toBe("2 votes");
|
||||
expect(votesCount(body, "poutine")).toBe("1 vote");
|
||||
expect(votesCount(body, "italian")).toBe("0 votes");
|
||||
expect(votesCount(body, "wings")).toBe("1 vote");
|
||||
expect(body.find(".mx_MPollBody_totalVotes").text()).toBe("Based on 4 votes");
|
||||
const renderResult = newMPollBody(votes);
|
||||
expect(votesCount(renderResult, "pizza")).toBe("2 votes");
|
||||
expect(votesCount(renderResult, "poutine")).toBe("1 vote");
|
||||
expect(votesCount(renderResult, "italian")).toBe("0 votes");
|
||||
expect(votesCount(renderResult, "wings")).toBe("1 vote");
|
||||
expect(renderResult.getByTestId("totalVotes").innerHTML).toBe("Based on 4 votes");
|
||||
});
|
||||
|
||||
it("ignores end poll events from unauthorised users", () => {
|
||||
|
@ -179,15 +180,15 @@ describe("MPollBody", () => {
|
|||
responseEvent("@dune2:example.com", "wings"),
|
||||
];
|
||||
const ends = [endEvent("@notallowed:example.com", 12)];
|
||||
const body = newMPollBody(votes, ends);
|
||||
const renderResult = newMPollBody(votes, ends);
|
||||
|
||||
// Even though an end event was sent, we render the poll as unfinished
|
||||
// because this person is not allowed to send these events
|
||||
expect(votesCount(body, "pizza")).toBe("2 votes");
|
||||
expect(votesCount(body, "poutine")).toBe("1 vote");
|
||||
expect(votesCount(body, "italian")).toBe("0 votes");
|
||||
expect(votesCount(body, "wings")).toBe("1 vote");
|
||||
expect(body.find(".mx_MPollBody_totalVotes").text()).toBe("Based on 4 votes");
|
||||
expect(votesCount(renderResult, "pizza")).toBe("2 votes");
|
||||
expect(votesCount(renderResult, "poutine")).toBe("1 vote");
|
||||
expect(votesCount(renderResult, "italian")).toBe("0 votes");
|
||||
expect(votesCount(renderResult, "wings")).toBe("1 vote");
|
||||
expect(renderResult.getByTestId("totalVotes").innerHTML).toBe("Based on 4 votes");
|
||||
});
|
||||
|
||||
it("hides scores if I have not voted", () => {
|
||||
|
@ -197,22 +198,22 @@ describe("MPollBody", () => {
|
|||
responseEvent("@catrd:example.com", "poutine"),
|
||||
responseEvent("@dune2:example.com", "wings"),
|
||||
];
|
||||
const body = newMPollBody(votes);
|
||||
expect(votesCount(body, "pizza")).toBe("");
|
||||
expect(votesCount(body, "poutine")).toBe("");
|
||||
expect(votesCount(body, "italian")).toBe("");
|
||||
expect(votesCount(body, "wings")).toBe("");
|
||||
expect(body.find(".mx_MPollBody_totalVotes").text()).toBe("4 votes cast. Vote to see the results");
|
||||
const renderResult = newMPollBody(votes);
|
||||
expect(votesCount(renderResult, "pizza")).toBe("");
|
||||
expect(votesCount(renderResult, "poutine")).toBe("");
|
||||
expect(votesCount(renderResult, "italian")).toBe("");
|
||||
expect(votesCount(renderResult, "wings")).toBe("");
|
||||
expect(renderResult.getByTestId("totalVotes").innerHTML).toBe("4 votes cast. Vote to see the results");
|
||||
});
|
||||
|
||||
it("hides a single vote if I have not voted", () => {
|
||||
const votes = [responseEvent("@alice:example.com", "pizza")];
|
||||
const body = newMPollBody(votes);
|
||||
expect(votesCount(body, "pizza")).toBe("");
|
||||
expect(votesCount(body, "poutine")).toBe("");
|
||||
expect(votesCount(body, "italian")).toBe("");
|
||||
expect(votesCount(body, "wings")).toBe("");
|
||||
expect(body.find(".mx_MPollBody_totalVotes").text()).toBe("1 vote cast. Vote to see the results");
|
||||
const renderResult = newMPollBody(votes);
|
||||
expect(votesCount(renderResult, "pizza")).toBe("");
|
||||
expect(votesCount(renderResult, "poutine")).toBe("");
|
||||
expect(votesCount(renderResult, "italian")).toBe("");
|
||||
expect(votesCount(renderResult, "wings")).toBe("");
|
||||
expect(renderResult.getByTestId("totalVotes").innerHTML).toBe("1 vote cast. Vote to see the results");
|
||||
});
|
||||
|
||||
it("takes someone's most recent vote if they voted several times", () => {
|
||||
|
@ -223,12 +224,12 @@ describe("MPollBody", () => {
|
|||
responseEvent("@qbert:example.com", "poutine", 16), // latest qbert
|
||||
responseEvent("@qbert:example.com", "wings", 15),
|
||||
];
|
||||
const body = newMPollBody(votes);
|
||||
expect(votesCount(body, "pizza")).toBe("0 votes");
|
||||
expect(votesCount(body, "poutine")).toBe("1 vote");
|
||||
expect(votesCount(body, "italian")).toBe("0 votes");
|
||||
expect(votesCount(body, "wings")).toBe("1 vote");
|
||||
expect(body.find(".mx_MPollBody_totalVotes").text()).toBe("Based on 2 votes");
|
||||
const renderResult = newMPollBody(votes);
|
||||
expect(votesCount(renderResult, "pizza")).toBe("0 votes");
|
||||
expect(votesCount(renderResult, "poutine")).toBe("1 vote");
|
||||
expect(votesCount(renderResult, "italian")).toBe("0 votes");
|
||||
expect(votesCount(renderResult, "wings")).toBe("1 vote");
|
||||
expect(renderResult.getByTestId("totalVotes").innerHTML).toBe("Based on 2 votes");
|
||||
});
|
||||
|
||||
it("uses my local vote", () => {
|
||||
|
@ -238,18 +239,18 @@ describe("MPollBody", () => {
|
|||
responseEvent("@fg:example.com", "pizza", 15),
|
||||
responseEvent("@hi:example.com", "pizza", 15),
|
||||
];
|
||||
const body = newMPollBody(votes);
|
||||
const renderResult = newMPollBody(votes);
|
||||
|
||||
// When I vote for Italian
|
||||
clickRadio(body, "italian");
|
||||
clickOption(renderResult, "italian");
|
||||
|
||||
// My vote is counted
|
||||
expect(votesCount(body, "pizza")).toBe("3 votes");
|
||||
expect(votesCount(body, "poutine")).toBe("0 votes");
|
||||
expect(votesCount(body, "italian")).toBe("1 vote");
|
||||
expect(votesCount(body, "wings")).toBe("0 votes");
|
||||
expect(votesCount(renderResult, "pizza")).toBe("3 votes");
|
||||
expect(votesCount(renderResult, "poutine")).toBe("0 votes");
|
||||
expect(votesCount(renderResult, "italian")).toBe("1 vote");
|
||||
expect(votesCount(renderResult, "wings")).toBe("0 votes");
|
||||
|
||||
expect(body.find(".mx_MPollBody_totalVotes").text()).toBe("Based on 4 votes");
|
||||
expect(renderResult.getByTestId("totalVotes").innerHTML).toBe("Based on 4 votes");
|
||||
});
|
||||
|
||||
it("overrides my other votes with my local vote", () => {
|
||||
|
@ -260,53 +261,66 @@ describe("MPollBody", () => {
|
|||
responseEvent("@me:example.com", "italian", 14),
|
||||
responseEvent("@nf:example.com", "italian", 15),
|
||||
];
|
||||
const body = newMPollBody(votes);
|
||||
const renderResult = newMPollBody(votes);
|
||||
|
||||
// When I click Wings
|
||||
clickRadio(body, "wings");
|
||||
clickOption(renderResult, "wings");
|
||||
|
||||
// Then my vote is counted for Wings, and not for Italian
|
||||
expect(votesCount(body, "pizza")).toBe("0 votes");
|
||||
expect(votesCount(body, "poutine")).toBe("0 votes");
|
||||
expect(votesCount(body, "italian")).toBe("1 vote");
|
||||
expect(votesCount(body, "wings")).toBe("1 vote");
|
||||
expect(votesCount(renderResult, "pizza")).toBe("0 votes");
|
||||
expect(votesCount(renderResult, "poutine")).toBe("0 votes");
|
||||
expect(votesCount(renderResult, "italian")).toBe("1 vote");
|
||||
expect(votesCount(renderResult, "wings")).toBe("1 vote");
|
||||
|
||||
expect(body.find(".mx_MPollBody_totalVotes").text()).toBe("Based on 2 votes");
|
||||
expect(renderResult.getByTestId("totalVotes").innerHTML).toBe("Based on 2 votes");
|
||||
|
||||
// And my vote is highlighted
|
||||
expect(voteButton(body, "wings").hasClass(CHECKED)).toBe(true);
|
||||
expect(voteButton(body, "italian").hasClass(CHECKED)).toBe(false);
|
||||
expect(voteButton(renderResult, "wings").className.includes(CHECKED)).toBe(true);
|
||||
expect(voteButton(renderResult, "italian").className.includes(CHECKED)).toBe(false);
|
||||
});
|
||||
|
||||
it("cancels my local vote if another comes in", () => {
|
||||
// Given I voted locally
|
||||
const votes = [responseEvent("@me:example.com", "pizza", 100)];
|
||||
const body = newMPollBody(votes);
|
||||
const props: IBodyProps = body.instance().props as IBodyProps;
|
||||
const mxEvent = new MatrixEvent({
|
||||
type: M_POLL_START.name,
|
||||
event_id: "$mypoll",
|
||||
room_id: "#myroom:example.com",
|
||||
content: newPollStart(undefined, undefined, true),
|
||||
});
|
||||
const props = getMPollBodyPropsFromEvent(mxEvent, votes);
|
||||
const renderResult = renderMPollBodyWithWrapper(props);
|
||||
const voteRelations = props!.getRelationsForEvent!("$mypoll", "m.reference", M_POLL_RESPONSE.name);
|
||||
expect(voteRelations).toBeDefined();
|
||||
clickRadio(body, "pizza");
|
||||
clickOption(renderResult, "pizza");
|
||||
|
||||
// When a new vote from me comes in
|
||||
voteRelations!.addEvent(responseEvent("@me:example.com", "wings", 101));
|
||||
|
||||
// Then the new vote is counted, not the old one
|
||||
expect(votesCount(body, "pizza")).toBe("0 votes");
|
||||
expect(votesCount(body, "poutine")).toBe("0 votes");
|
||||
expect(votesCount(body, "italian")).toBe("0 votes");
|
||||
expect(votesCount(body, "wings")).toBe("1 vote");
|
||||
expect(votesCount(renderResult, "pizza")).toBe("0 votes");
|
||||
expect(votesCount(renderResult, "poutine")).toBe("0 votes");
|
||||
expect(votesCount(renderResult, "italian")).toBe("0 votes");
|
||||
expect(votesCount(renderResult, "wings")).toBe("1 vote");
|
||||
|
||||
expect(body.find(".mx_MPollBody_totalVotes").text()).toBe("Based on 1 vote");
|
||||
expect(renderResult.getByTestId("totalVotes").innerHTML).toBe("Based on 1 vote");
|
||||
});
|
||||
|
||||
it("doesn't cancel my local vote if someone else votes", () => {
|
||||
// Given I voted locally
|
||||
const votes = [responseEvent("@me:example.com", "pizza")];
|
||||
const body = newMPollBody(votes);
|
||||
const props: IBodyProps = body.instance().props as IBodyProps;
|
||||
const mxEvent = new MatrixEvent({
|
||||
type: M_POLL_START.name,
|
||||
event_id: "$mypoll",
|
||||
room_id: "#myroom:example.com",
|
||||
content: newPollStart(undefined, undefined, true),
|
||||
});
|
||||
const props = getMPollBodyPropsFromEvent(mxEvent, votes);
|
||||
const renderResult = renderMPollBodyWithWrapper(props);
|
||||
|
||||
const voteRelations = props!.getRelationsForEvent!("$mypoll", "m.reference", M_POLL_RESPONSE.name);
|
||||
expect(voteRelations).toBeDefined();
|
||||
clickRadio(body, "pizza");
|
||||
clickOption(renderResult, "pizza");
|
||||
|
||||
// When a new vote from someone else comes in
|
||||
voteRelations!.addEvent(responseEvent("@xx:example.com", "wings", 101));
|
||||
|
@ -315,39 +329,39 @@ describe("MPollBody", () => {
|
|||
// NOTE: the new event does not affect the counts for other people -
|
||||
// that is handled through the Relations, not by listening to
|
||||
// these timeline events.
|
||||
expect(votesCount(body, "pizza")).toBe("1 vote");
|
||||
expect(votesCount(body, "poutine")).toBe("0 votes");
|
||||
expect(votesCount(body, "italian")).toBe("0 votes");
|
||||
expect(votesCount(body, "wings")).toBe("1 vote");
|
||||
expect(votesCount(renderResult, "pizza")).toBe("1 vote");
|
||||
expect(votesCount(renderResult, "poutine")).toBe("0 votes");
|
||||
expect(votesCount(renderResult, "italian")).toBe("0 votes");
|
||||
expect(votesCount(renderResult, "wings")).toBe("1 vote");
|
||||
|
||||
expect(body.find(".mx_MPollBody_totalVotes").text()).toBe("Based on 2 votes");
|
||||
expect(renderResult.getByTestId("totalVotes").innerHTML).toBe("Based on 2 votes");
|
||||
|
||||
// And my vote is highlighted
|
||||
expect(voteButton(body, "pizza").hasClass(CHECKED)).toBe(true);
|
||||
expect(voteButton(body, "wings").hasClass(CHECKED)).toBe(false);
|
||||
expect(voteButton(renderResult, "pizza").className.includes(CHECKED)).toBe(true);
|
||||
expect(voteButton(renderResult, "wings").className.includes(CHECKED)).toBe(false);
|
||||
});
|
||||
|
||||
it("highlights my vote even if I did it on another device", () => {
|
||||
// Given I voted italian
|
||||
const votes = [responseEvent("@me:example.com", "italian"), responseEvent("@nf:example.com", "wings")];
|
||||
const body = newMPollBody(votes);
|
||||
const renderResult = newMPollBody(votes);
|
||||
|
||||
// But I didn't click anything locally
|
||||
|
||||
// Then my vote is highlighted, and others are not
|
||||
expect(voteButton(body, "italian").hasClass(CHECKED)).toBe(true);
|
||||
expect(voteButton(body, "wings").hasClass(CHECKED)).toBe(false);
|
||||
expect(voteButton(renderResult, "italian").className.includes(CHECKED)).toBe(true);
|
||||
expect(voteButton(renderResult, "wings").className.includes(CHECKED)).toBe(false);
|
||||
});
|
||||
|
||||
it("ignores extra answers", () => {
|
||||
// When cb votes for 2 things, we consider the first only
|
||||
const votes = [responseEvent("@cb:example.com", ["pizza", "wings"]), responseEvent("@me:example.com", "wings")];
|
||||
const body = newMPollBody(votes);
|
||||
expect(votesCount(body, "pizza")).toBe("1 vote");
|
||||
expect(votesCount(body, "poutine")).toBe("0 votes");
|
||||
expect(votesCount(body, "italian")).toBe("0 votes");
|
||||
expect(votesCount(body, "wings")).toBe("1 vote");
|
||||
expect(body.find(".mx_MPollBody_totalVotes").text()).toBe("Based on 2 votes");
|
||||
const renderResult = newMPollBody(votes);
|
||||
expect(votesCount(renderResult, "pizza")).toBe("1 vote");
|
||||
expect(votesCount(renderResult, "poutine")).toBe("0 votes");
|
||||
expect(votesCount(renderResult, "italian")).toBe("0 votes");
|
||||
expect(votesCount(renderResult, "wings")).toBe("1 vote");
|
||||
expect(renderResult.getByTestId("totalVotes").innerHTML).toBe("Based on 2 votes");
|
||||
});
|
||||
|
||||
it("allows un-voting by passing an empty vote", () => {
|
||||
|
@ -356,12 +370,12 @@ describe("MPollBody", () => {
|
|||
responseEvent("@nc:example.com", [], 13),
|
||||
responseEvent("@me:example.com", "italian"),
|
||||
];
|
||||
const body = newMPollBody(votes);
|
||||
expect(votesCount(body, "pizza")).toBe("0 votes");
|
||||
expect(votesCount(body, "poutine")).toBe("0 votes");
|
||||
expect(votesCount(body, "italian")).toBe("1 vote");
|
||||
expect(votesCount(body, "wings")).toBe("0 votes");
|
||||
expect(body.find(".mx_MPollBody_totalVotes").text()).toBe("Based on 1 vote");
|
||||
const renderResult = newMPollBody(votes);
|
||||
expect(votesCount(renderResult, "pizza")).toBe("0 votes");
|
||||
expect(votesCount(renderResult, "poutine")).toBe("0 votes");
|
||||
expect(votesCount(renderResult, "italian")).toBe("1 vote");
|
||||
expect(votesCount(renderResult, "wings")).toBe("0 votes");
|
||||
expect(renderResult.getByTestId("totalVotes").innerHTML).toBe("Based on 1 vote");
|
||||
});
|
||||
|
||||
it("allows re-voting after un-voting", () => {
|
||||
|
@ -371,12 +385,12 @@ describe("MPollBody", () => {
|
|||
responseEvent("@op:example.com", "italian", 14),
|
||||
responseEvent("@me:example.com", "italian"),
|
||||
];
|
||||
const body = newMPollBody(votes);
|
||||
expect(votesCount(body, "pizza")).toBe("0 votes");
|
||||
expect(votesCount(body, "poutine")).toBe("0 votes");
|
||||
expect(votesCount(body, "italian")).toBe("2 votes");
|
||||
expect(votesCount(body, "wings")).toBe("0 votes");
|
||||
expect(body.find(".mx_MPollBody_totalVotes").text()).toBe("Based on 2 votes");
|
||||
const renderResult = newMPollBody(votes);
|
||||
expect(votesCount(renderResult, "pizza")).toBe("0 votes");
|
||||
expect(votesCount(renderResult, "poutine")).toBe("0 votes");
|
||||
expect(votesCount(renderResult, "italian")).toBe("2 votes");
|
||||
expect(votesCount(renderResult, "wings")).toBe("0 votes");
|
||||
expect(renderResult.getByTestId("totalVotes").innerHTML).toBe("Based on 2 votes");
|
||||
});
|
||||
|
||||
it("treats any invalid answer as a spoiled ballot", () => {
|
||||
|
@ -389,12 +403,12 @@ describe("MPollBody", () => {
|
|||
responseEvent("@uy:example.com", "italian", 14),
|
||||
responseEvent("@uy:example.com", "doesntexist", 15),
|
||||
];
|
||||
const body = newMPollBody(votes);
|
||||
expect(votesCount(body, "pizza")).toBe("0 votes");
|
||||
expect(votesCount(body, "poutine")).toBe("0 votes");
|
||||
expect(votesCount(body, "italian")).toBe("0 votes");
|
||||
expect(votesCount(body, "wings")).toBe("0 votes");
|
||||
expect(body.find(".mx_MPollBody_totalVotes").text()).toBe("Based on 0 votes");
|
||||
const renderResult = newMPollBody(votes);
|
||||
expect(votesCount(renderResult, "pizza")).toBe("0 votes");
|
||||
expect(votesCount(renderResult, "poutine")).toBe("0 votes");
|
||||
expect(votesCount(renderResult, "italian")).toBe("0 votes");
|
||||
expect(votesCount(renderResult, "wings")).toBe("0 votes");
|
||||
expect(renderResult.getByTestId("totalVotes").innerHTML).toBe("Based on 0 votes");
|
||||
});
|
||||
|
||||
it("allows re-voting after a spoiled ballot", () => {
|
||||
|
@ -405,31 +419,31 @@ describe("MPollBody", () => {
|
|||
responseEvent("@uy:example.com", "doesntexist", 15),
|
||||
responseEvent("@uy:example.com", "poutine", 16),
|
||||
];
|
||||
const body = newMPollBody(votes);
|
||||
expect(body.find('input[type="radio"]')).toHaveLength(4);
|
||||
expect(votesCount(body, "pizza")).toBe("0 votes");
|
||||
expect(votesCount(body, "poutine")).toBe("1 vote");
|
||||
expect(votesCount(body, "italian")).toBe("0 votes");
|
||||
expect(votesCount(body, "wings")).toBe("0 votes");
|
||||
expect(body.find(".mx_MPollBody_totalVotes").text()).toBe("Based on 1 vote");
|
||||
const renderResult = newMPollBody(votes);
|
||||
expect(renderResult.container.querySelectorAll('input[type="radio"]')).toHaveLength(4);
|
||||
expect(votesCount(renderResult, "pizza")).toBe("0 votes");
|
||||
expect(votesCount(renderResult, "poutine")).toBe("1 vote");
|
||||
expect(votesCount(renderResult, "italian")).toBe("0 votes");
|
||||
expect(votesCount(renderResult, "wings")).toBe("0 votes");
|
||||
expect(renderResult.getByTestId("totalVotes").innerHTML).toBe("Based on 1 vote");
|
||||
});
|
||||
|
||||
it("renders nothing if poll has no answers", () => {
|
||||
const answers = [];
|
||||
const votes = [];
|
||||
const ends = [];
|
||||
const body = newMPollBody(votes, ends, answers);
|
||||
expect(body.html()).toBeNull();
|
||||
const answers: PollAnswer[] = [];
|
||||
const votes: MatrixEvent[] = [];
|
||||
const ends: MatrixEvent[] = [];
|
||||
const { container } = newMPollBody(votes, ends, answers);
|
||||
expect(container.childElementCount).toEqual(0);
|
||||
});
|
||||
|
||||
it("renders the first 20 answers if 21 were given", () => {
|
||||
const answers = Array.from(Array(21).keys()).map((i) => {
|
||||
return { id: `id${i}`, [M_TEXT.name]: `Name ${i}` };
|
||||
});
|
||||
const votes = [];
|
||||
const ends = [];
|
||||
const body = newMPollBody(votes, ends, answers);
|
||||
expect(body.find(".mx_MPollBody_option").length).toBe(20);
|
||||
const votes: MatrixEvent[] = [];
|
||||
const ends: MatrixEvent[] = [];
|
||||
const { container } = newMPollBody(votes, ends, answers);
|
||||
expect(container.querySelectorAll(".mx_MPollBody_option").length).toBe(20);
|
||||
});
|
||||
|
||||
it("hides scores if I voted but the poll is undisclosed", () => {
|
||||
|
@ -440,12 +454,12 @@ describe("MPollBody", () => {
|
|||
responseEvent("@catrd:example.com", "poutine"),
|
||||
responseEvent("@dune2:example.com", "wings"),
|
||||
];
|
||||
const body = newMPollBody(votes, [], null, false);
|
||||
expect(votesCount(body, "pizza")).toBe("");
|
||||
expect(votesCount(body, "poutine")).toBe("");
|
||||
expect(votesCount(body, "italian")).toBe("");
|
||||
expect(votesCount(body, "wings")).toBe("");
|
||||
expect(body.find(".mx_MPollBody_totalVotes").text()).toBe("Results will be visible when the poll is ended");
|
||||
const renderResult = newMPollBody(votes, [], undefined, false);
|
||||
expect(votesCount(renderResult, "pizza")).toBe("");
|
||||
expect(votesCount(renderResult, "poutine")).toBe("");
|
||||
expect(votesCount(renderResult, "italian")).toBe("");
|
||||
expect(votesCount(renderResult, "wings")).toBe("");
|
||||
expect(renderResult.getByTestId("totalVotes").innerHTML).toBe("Results will be visible when the poll is ended");
|
||||
});
|
||||
|
||||
it("highlights my vote if the poll is undisclosed", () => {
|
||||
|
@ -456,13 +470,13 @@ describe("MPollBody", () => {
|
|||
responseEvent("@catrd:example.com", "poutine"),
|
||||
responseEvent("@dune2:example.com", "wings"),
|
||||
];
|
||||
const body = newMPollBody(votes, [], null, false);
|
||||
const { container } = newMPollBody(votes, [], undefined, false);
|
||||
|
||||
// My vote is marked
|
||||
expect(body.find('input[value="pizza"]').prop("checked")).toBeTruthy();
|
||||
expect(container.querySelector('input[value="pizza"]')!).toBeChecked();
|
||||
|
||||
// Sanity: other items are not checked
|
||||
expect(body.find('input[value="poutine"]').prop("checked")).toBeFalsy();
|
||||
expect(container.querySelector('input[value="poutine"]')!).not.toBeChecked();
|
||||
});
|
||||
|
||||
it("shows scores if the poll is undisclosed but ended", () => {
|
||||
|
@ -474,47 +488,47 @@ describe("MPollBody", () => {
|
|||
responseEvent("@dune2:example.com", "wings"),
|
||||
];
|
||||
const ends = [endEvent("@me:example.com", 12)];
|
||||
const body = newMPollBody(votes, ends, null, false);
|
||||
expect(endedVotesCount(body, "pizza")).toBe("3 votes");
|
||||
expect(endedVotesCount(body, "poutine")).toBe("1 vote");
|
||||
expect(endedVotesCount(body, "italian")).toBe("0 votes");
|
||||
expect(endedVotesCount(body, "wings")).toBe("1 vote");
|
||||
expect(body.find(".mx_MPollBody_totalVotes").text()).toBe("Final result based on 5 votes");
|
||||
const renderResult = newMPollBody(votes, ends, undefined, false);
|
||||
expect(endedVotesCount(renderResult, "pizza")).toBe("3 votes");
|
||||
expect(endedVotesCount(renderResult, "poutine")).toBe("1 vote");
|
||||
expect(endedVotesCount(renderResult, "italian")).toBe("0 votes");
|
||||
expect(endedVotesCount(renderResult, "wings")).toBe("1 vote");
|
||||
expect(renderResult.getByTestId("totalVotes").innerHTML).toBe("Final result based on 5 votes");
|
||||
});
|
||||
|
||||
it("sends a vote event when I choose an option", () => {
|
||||
const votes = [];
|
||||
const body = newMPollBody(votes);
|
||||
clickRadio(body, "wings");
|
||||
const votes: MatrixEvent[] = [];
|
||||
const renderResult = newMPollBody(votes);
|
||||
clickOption(renderResult, "wings");
|
||||
expect(mockClient.sendEvent).toHaveBeenCalledWith(...expectedResponseEventCall("wings"));
|
||||
});
|
||||
|
||||
it("sends only one vote event when I click several times", () => {
|
||||
const votes = [];
|
||||
const body = newMPollBody(votes);
|
||||
clickRadio(body, "wings");
|
||||
clickRadio(body, "wings");
|
||||
clickRadio(body, "wings");
|
||||
clickRadio(body, "wings");
|
||||
const votes: MatrixEvent[] = [];
|
||||
const renderResult = newMPollBody(votes);
|
||||
clickOption(renderResult, "wings");
|
||||
clickOption(renderResult, "wings");
|
||||
clickOption(renderResult, "wings");
|
||||
clickOption(renderResult, "wings");
|
||||
expect(mockClient.sendEvent).toHaveBeenCalledWith(...expectedResponseEventCall("wings"));
|
||||
});
|
||||
|
||||
it("sends no vote event when I click what I already chose", () => {
|
||||
const votes = [responseEvent("@me:example.com", "wings")];
|
||||
const body = newMPollBody(votes);
|
||||
clickRadio(body, "wings");
|
||||
clickRadio(body, "wings");
|
||||
clickRadio(body, "wings");
|
||||
clickRadio(body, "wings");
|
||||
const renderResult = newMPollBody(votes);
|
||||
clickOption(renderResult, "wings");
|
||||
clickOption(renderResult, "wings");
|
||||
clickOption(renderResult, "wings");
|
||||
clickOption(renderResult, "wings");
|
||||
expect(mockClient.sendEvent).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("sends several events when I click different options", () => {
|
||||
const votes = [];
|
||||
const body = newMPollBody(votes);
|
||||
clickRadio(body, "wings");
|
||||
clickRadio(body, "italian");
|
||||
clickRadio(body, "poutine");
|
||||
const votes: MatrixEvent[] = [];
|
||||
const renderResult = newMPollBody(votes);
|
||||
clickOption(renderResult, "wings");
|
||||
clickOption(renderResult, "italian");
|
||||
clickOption(renderResult, "poutine");
|
||||
expect(mockClient.sendEvent).toHaveBeenCalledTimes(3);
|
||||
expect(mockClient.sendEvent).toHaveBeenCalledWith(...expectedResponseEventCall("wings"));
|
||||
expect(mockClient.sendEvent).toHaveBeenCalledWith(...expectedResponseEventCall("italian"));
|
||||
|
@ -524,10 +538,10 @@ describe("MPollBody", () => {
|
|||
it("sends no events when I click in an ended poll", () => {
|
||||
const ends = [endEvent("@me:example.com", 25)];
|
||||
const votes = [responseEvent("@uy:example.com", "wings", 15), responseEvent("@uy:example.com", "poutine", 15)];
|
||||
const body = newMPollBody(votes, ends);
|
||||
clickEndedOption(body, "wings");
|
||||
clickEndedOption(body, "italian");
|
||||
clickEndedOption(body, "poutine");
|
||||
const renderResult = newMPollBody(votes, ends);
|
||||
clickOption(renderResult, "wings");
|
||||
clickOption(renderResult, "italian");
|
||||
clickOption(renderResult, "poutine");
|
||||
expect(mockClient.sendEvent).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
@ -577,9 +591,9 @@ describe("MPollBody", () => {
|
|||
|
||||
it("shows non-radio buttons if the poll is ended", () => {
|
||||
const events = [endEvent()];
|
||||
const body = newMPollBody([], events);
|
||||
expect(body.find(".mx_StyledRadioButton")).toHaveLength(0);
|
||||
expect(body.find('input[type="radio"]')).toHaveLength(0);
|
||||
const { container } = newMPollBody([], events);
|
||||
expect(container.querySelector(".mx_StyledRadioButton")).not.toBeInTheDocument();
|
||||
expect(container.querySelector('input[type="radio"]')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("counts votes as normal if the poll is ended", () => {
|
||||
|
@ -591,23 +605,23 @@ describe("MPollBody", () => {
|
|||
responseEvent("@qbert:example.com", "wings", 15),
|
||||
];
|
||||
const ends = [endEvent("@me:example.com", 25)];
|
||||
const body = newMPollBody(votes, ends);
|
||||
expect(endedVotesCount(body, "pizza")).toBe("0 votes");
|
||||
expect(endedVotesCount(body, "poutine")).toBe("1 vote");
|
||||
expect(endedVotesCount(body, "italian")).toBe("0 votes");
|
||||
expect(endedVotesCount(body, "wings")).toBe("1 vote");
|
||||
expect(body.find(".mx_MPollBody_totalVotes").text()).toBe("Final result based on 2 votes");
|
||||
const renderResult = newMPollBody(votes, ends);
|
||||
expect(endedVotesCount(renderResult, "pizza")).toBe("0 votes");
|
||||
expect(endedVotesCount(renderResult, "poutine")).toBe("1 vote");
|
||||
expect(endedVotesCount(renderResult, "italian")).toBe("0 votes");
|
||||
expect(endedVotesCount(renderResult, "wings")).toBe("1 vote");
|
||||
expect(renderResult.getByTestId("totalVotes").innerHTML).toBe("Final result based on 2 votes");
|
||||
});
|
||||
|
||||
it("counts a single vote as normal if the poll is ended", () => {
|
||||
const votes = [responseEvent("@qbert:example.com", "poutine", 16)];
|
||||
const ends = [endEvent("@me:example.com", 25)];
|
||||
const body = newMPollBody(votes, ends);
|
||||
expect(endedVotesCount(body, "pizza")).toBe("0 votes");
|
||||
expect(endedVotesCount(body, "poutine")).toBe("1 vote");
|
||||
expect(endedVotesCount(body, "italian")).toBe("0 votes");
|
||||
expect(endedVotesCount(body, "wings")).toBe("0 votes");
|
||||
expect(body.find(".mx_MPollBody_totalVotes").text()).toBe("Final result based on 1 vote");
|
||||
const renderResult = newMPollBody(votes, ends);
|
||||
expect(endedVotesCount(renderResult, "pizza")).toBe("0 votes");
|
||||
expect(endedVotesCount(renderResult, "poutine")).toBe("1 vote");
|
||||
expect(endedVotesCount(renderResult, "italian")).toBe("0 votes");
|
||||
expect(endedVotesCount(renderResult, "wings")).toBe("0 votes");
|
||||
expect(renderResult.getByTestId("totalVotes").innerHTML).toBe("Final result based on 1 vote");
|
||||
});
|
||||
|
||||
it("shows ended vote counts of different numbers", () => {
|
||||
|
@ -619,15 +633,15 @@ describe("MPollBody", () => {
|
|||
responseEvent("@hi:example.com", "pizza", 15),
|
||||
];
|
||||
const ends = [endEvent("@me:example.com", 25)];
|
||||
const body = newMPollBody(votes, ends);
|
||||
const renderResult = newMPollBody(votes, ends);
|
||||
|
||||
expect(body.find(".mx_StyledRadioButton")).toHaveLength(0);
|
||||
expect(body.find('input[type="radio"]')).toHaveLength(0);
|
||||
expect(endedVotesCount(body, "pizza")).toBe("2 votes");
|
||||
expect(endedVotesCount(body, "poutine")).toBe("0 votes");
|
||||
expect(endedVotesCount(body, "italian")).toBe("0 votes");
|
||||
expect(endedVotesCount(body, "wings")).toBe("3 votes");
|
||||
expect(body.find(".mx_MPollBody_totalVotes").text()).toBe("Final result based on 5 votes");
|
||||
expect(renderResult.container.querySelectorAll(".mx_StyledRadioButton")).toHaveLength(0);
|
||||
expect(renderResult.container.querySelectorAll('input[type="radio"]')).toHaveLength(0);
|
||||
expect(endedVotesCount(renderResult, "pizza")).toBe("2 votes");
|
||||
expect(endedVotesCount(renderResult, "poutine")).toBe("0 votes");
|
||||
expect(endedVotesCount(renderResult, "italian")).toBe("0 votes");
|
||||
expect(endedVotesCount(renderResult, "wings")).toBe("3 votes");
|
||||
expect(renderResult.getByTestId("totalVotes").innerHTML).toBe("Final result based on 5 votes");
|
||||
});
|
||||
|
||||
it("ignores votes that arrived after poll ended", () => {
|
||||
|
@ -641,13 +655,13 @@ describe("MPollBody", () => {
|
|||
responseEvent("@ld:example.com", "pizza", 15),
|
||||
];
|
||||
const ends = [endEvent("@me:example.com", 25)];
|
||||
const body = newMPollBody(votes, ends);
|
||||
const renderResult = newMPollBody(votes, ends);
|
||||
|
||||
expect(endedVotesCount(body, "pizza")).toBe("2 votes");
|
||||
expect(endedVotesCount(body, "poutine")).toBe("0 votes");
|
||||
expect(endedVotesCount(body, "italian")).toBe("0 votes");
|
||||
expect(endedVotesCount(body, "wings")).toBe("3 votes");
|
||||
expect(body.find(".mx_MPollBody_totalVotes").text()).toBe("Final result based on 5 votes");
|
||||
expect(endedVotesCount(renderResult, "pizza")).toBe("2 votes");
|
||||
expect(endedVotesCount(renderResult, "poutine")).toBe("0 votes");
|
||||
expect(endedVotesCount(renderResult, "italian")).toBe("0 votes");
|
||||
expect(endedVotesCount(renderResult, "wings")).toBe("3 votes");
|
||||
expect(renderResult.getByTestId("totalVotes").innerHTML).toBe("Final result based on 5 votes");
|
||||
});
|
||||
|
||||
it("counts votes that arrived after an unauthorised poll end event", () => {
|
||||
|
@ -664,13 +678,13 @@ describe("MPollBody", () => {
|
|||
endEvent("@unauthorised:example.com", 5), // Should be ignored
|
||||
endEvent("@me:example.com", 25),
|
||||
];
|
||||
const body = newMPollBody(votes, ends);
|
||||
const renderResult = newMPollBody(votes, ends);
|
||||
|
||||
expect(endedVotesCount(body, "pizza")).toBe("2 votes");
|
||||
expect(endedVotesCount(body, "poutine")).toBe("0 votes");
|
||||
expect(endedVotesCount(body, "italian")).toBe("0 votes");
|
||||
expect(endedVotesCount(body, "wings")).toBe("3 votes");
|
||||
expect(body.find(".mx_MPollBody_totalVotes").text()).toBe("Final result based on 5 votes");
|
||||
expect(endedVotesCount(renderResult, "pizza")).toBe("2 votes");
|
||||
expect(endedVotesCount(renderResult, "poutine")).toBe("0 votes");
|
||||
expect(endedVotesCount(renderResult, "italian")).toBe("0 votes");
|
||||
expect(endedVotesCount(renderResult, "wings")).toBe("3 votes");
|
||||
expect(renderResult.getByTestId("totalVotes").innerHTML).toBe("Final result based on 5 votes");
|
||||
});
|
||||
|
||||
it("ignores votes that arrived after the first end poll event", () => {
|
||||
|
@ -691,13 +705,13 @@ describe("MPollBody", () => {
|
|||
endEvent("@me:example.com", 25),
|
||||
endEvent("@me:example.com", 75),
|
||||
];
|
||||
const body = newMPollBody(votes, ends);
|
||||
const renderResult = newMPollBody(votes, ends);
|
||||
|
||||
expect(endedVotesCount(body, "pizza")).toBe("2 votes");
|
||||
expect(endedVotesCount(body, "poutine")).toBe("0 votes");
|
||||
expect(endedVotesCount(body, "italian")).toBe("0 votes");
|
||||
expect(endedVotesCount(body, "wings")).toBe("3 votes");
|
||||
expect(body.find(".mx_MPollBody_totalVotes").text()).toBe("Final result based on 5 votes");
|
||||
expect(endedVotesCount(renderResult, "pizza")).toBe("2 votes");
|
||||
expect(endedVotesCount(renderResult, "poutine")).toBe("0 votes");
|
||||
expect(endedVotesCount(renderResult, "italian")).toBe("0 votes");
|
||||
expect(endedVotesCount(renderResult, "wings")).toBe("3 votes");
|
||||
expect(renderResult.getByTestId("totalVotes").innerHTML).toBe("Final result based on 5 votes");
|
||||
});
|
||||
|
||||
it("highlights the winning vote in an ended poll", () => {
|
||||
|
@ -708,15 +722,15 @@ describe("MPollBody", () => {
|
|||
responseEvent("@xy:example.com", "wings", 15),
|
||||
];
|
||||
const ends = [endEvent("@me:example.com", 25)];
|
||||
const body = newMPollBody(votes, ends);
|
||||
const renderResult = newMPollBody(votes, ends);
|
||||
|
||||
// Then the winner is highlighted
|
||||
expect(endedVoteChecked(body, "wings")).toBe(true);
|
||||
expect(endedVoteChecked(body, "pizza")).toBe(false);
|
||||
expect(endedVoteChecked(renderResult, "wings")).toBe(true);
|
||||
expect(endedVoteChecked(renderResult, "pizza")).toBe(false);
|
||||
|
||||
// Double-check by looking for the endedOptionWinner class
|
||||
expect(endedVoteDiv(body, "wings").hasClass("mx_MPollBody_endedOptionWinner")).toBe(true);
|
||||
expect(endedVoteDiv(body, "pizza").hasClass("mx_MPollBody_endedOptionWinner")).toBe(false);
|
||||
expect(endedVoteDiv(renderResult, "wings").className.includes("mx_MPollBody_endedOptionWinner")).toBe(true);
|
||||
expect(endedVoteDiv(renderResult, "pizza").className.includes("mx_MPollBody_endedOptionWinner")).toBe(false);
|
||||
});
|
||||
|
||||
it("highlights multiple winning votes", () => {
|
||||
|
@ -726,23 +740,23 @@ describe("MPollBody", () => {
|
|||
responseEvent("@fg:example.com", "poutine", 15),
|
||||
];
|
||||
const ends = [endEvent("@me:example.com", 25)];
|
||||
const body = newMPollBody(votes, ends);
|
||||
const renderResult = newMPollBody(votes, ends);
|
||||
|
||||
expect(endedVoteChecked(body, "pizza")).toBe(true);
|
||||
expect(endedVoteChecked(body, "wings")).toBe(true);
|
||||
expect(endedVoteChecked(body, "poutine")).toBe(true);
|
||||
expect(endedVoteChecked(body, "italian")).toBe(false);
|
||||
expect(body.find(".mx_MPollBody_option_checked")).toHaveLength(3);
|
||||
expect(endedVoteChecked(renderResult, "pizza")).toBe(true);
|
||||
expect(endedVoteChecked(renderResult, "wings")).toBe(true);
|
||||
expect(endedVoteChecked(renderResult, "poutine")).toBe(true);
|
||||
expect(endedVoteChecked(renderResult, "italian")).toBe(false);
|
||||
expect(renderResult.container.getElementsByClassName("mx_MPollBody_option_checked")).toHaveLength(3);
|
||||
});
|
||||
|
||||
it("highlights nothing if poll has no votes", () => {
|
||||
const ends = [endEvent("@me:example.com", 25)];
|
||||
const body = newMPollBody([], ends);
|
||||
expect(body.find(".mx_MPollBody_option_checked")).toHaveLength(0);
|
||||
const renderResult = newMPollBody([], ends);
|
||||
expect(renderResult.container.getElementsByClassName("mx_MPollBody_option_checked")).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("says poll is not ended if there is no end event", () => {
|
||||
const ends = [];
|
||||
const ends: MatrixEvent[] = [];
|
||||
expect(runIsPollEnded(ends)).toBe(false);
|
||||
});
|
||||
|
||||
|
@ -810,26 +824,26 @@ describe("MPollBody", () => {
|
|||
},
|
||||
});
|
||||
pollEvent.makeReplaced(replacingEvent);
|
||||
const body = newMPollBodyFromEvent(pollEvent, []);
|
||||
expect(body.find("h2").html()).toEqual(
|
||||
"<h2>new question" + '<span class="mx_MPollBody_edited"> (edited)</span>' + "</h2>",
|
||||
const { getByTestId, container } = newMPollBodyFromEvent(pollEvent, []);
|
||||
expect(getByTestId("pollQuestion").innerHTML).toEqual(
|
||||
'new question<span class="mx_MPollBody_edited"> (edited)</span>',
|
||||
);
|
||||
const inputs = body.find('input[type="radio"]');
|
||||
const inputs = container.querySelectorAll('input[type="radio"]');
|
||||
expect(inputs).toHaveLength(3);
|
||||
expect(inputs.at(0).prop("value")).toEqual("n1");
|
||||
expect(inputs.at(1).prop("value")).toEqual("n2");
|
||||
expect(inputs.at(2).prop("value")).toEqual("n3");
|
||||
const options = body.find(".mx_MPollBody_optionText");
|
||||
expect(inputs[0].getAttribute("value")).toEqual("n1");
|
||||
expect(inputs[1].getAttribute("value")).toEqual("n2");
|
||||
expect(inputs[2].getAttribute("value")).toEqual("n3");
|
||||
const options = container.querySelectorAll(".mx_MPollBody_optionText");
|
||||
expect(options).toHaveLength(3);
|
||||
expect(options.at(0).text()).toEqual("new answer 1");
|
||||
expect(options.at(1).text()).toEqual("new answer 2");
|
||||
expect(options.at(2).text()).toEqual("new answer 3");
|
||||
expect(options[0].innerHTML).toEqual("new answer 1");
|
||||
expect(options[1].innerHTML).toEqual("new answer 2");
|
||||
expect(options[2].innerHTML).toEqual("new answer 3");
|
||||
});
|
||||
|
||||
it("renders a poll with no votes", () => {
|
||||
const votes = [];
|
||||
const body = newMPollBody(votes);
|
||||
expect(body).toMatchSnapshot();
|
||||
const votes: MatrixEvent[] = [];
|
||||
const { container } = newMPollBody(votes);
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders a poll with only non-local votes", () => {
|
||||
|
@ -840,8 +854,8 @@ describe("MPollBody", () => {
|
|||
responseEvent("@me:example.com", "wings", 15),
|
||||
responseEvent("@qr:example.com", "italian", 16),
|
||||
];
|
||||
const body = newMPollBody(votes);
|
||||
expect(body).toMatchSnapshot();
|
||||
const { container } = newMPollBody(votes);
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders a poll with local, non-local and invalid votes", () => {
|
||||
|
@ -853,9 +867,9 @@ describe("MPollBody", () => {
|
|||
responseEvent("@e:example.com", "wings", 15),
|
||||
responseEvent("@me:example.com", "italian", 16),
|
||||
];
|
||||
const body = newMPollBody(votes);
|
||||
clickRadio(body, "italian");
|
||||
expect(body).toMatchSnapshot();
|
||||
const renderResult = newMPollBody(votes);
|
||||
clickOption(renderResult, "italian");
|
||||
expect(renderResult.container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders a poll that I have not voted in", () => {
|
||||
|
@ -866,14 +880,14 @@ describe("MPollBody", () => {
|
|||
responseEvent("@yo:example.com", "wings", 15),
|
||||
responseEvent("@qr:example.com", "italian", 16),
|
||||
];
|
||||
const body = newMPollBody(votes);
|
||||
expect(body).toMatchSnapshot();
|
||||
const { container } = newMPollBody(votes);
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders a finished poll with no votes", () => {
|
||||
const ends = [endEvent("@me:example.com", 25)];
|
||||
const body = newMPollBody([], ends);
|
||||
expect(body).toMatchSnapshot();
|
||||
const { container } = newMPollBody([], ends);
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders a finished poll", () => {
|
||||
|
@ -885,8 +899,8 @@ describe("MPollBody", () => {
|
|||
responseEvent("@qr:example.com", "italian", 16),
|
||||
];
|
||||
const ends = [endEvent("@me:example.com", 25)];
|
||||
const body = newMPollBody(votes, ends);
|
||||
expect(body).toMatchSnapshot();
|
||||
const { container } = newMPollBody(votes, ends);
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders a finished poll with multiple winners", () => {
|
||||
|
@ -899,8 +913,8 @@ describe("MPollBody", () => {
|
|||
responseEvent("@yh:example.com", "poutine", 14),
|
||||
];
|
||||
const ends = [endEvent("@me:example.com", 25)];
|
||||
const body = newMPollBody(votes, ends);
|
||||
expect(body).toMatchSnapshot();
|
||||
const { container } = newMPollBody(votes, ends);
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders an undisclosed, unfinished poll", () => {
|
||||
|
@ -912,9 +926,9 @@ describe("MPollBody", () => {
|
|||
responseEvent("@th:example.com", "poutine", 13),
|
||||
responseEvent("@yh:example.com", "poutine", 14),
|
||||
];
|
||||
const ends = [];
|
||||
const body = newMPollBody(votes, ends, null, false);
|
||||
expect(body.html()).toMatchSnapshot();
|
||||
const ends: MatrixEvent[] = [];
|
||||
const { container } = newMPollBody(votes, ends, undefined, false);
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders an undisclosed, finished poll", () => {
|
||||
|
@ -927,8 +941,8 @@ describe("MPollBody", () => {
|
|||
responseEvent("@yh:example.com", "poutine", 14),
|
||||
];
|
||||
const ends = [endEvent("@me:example.com", 25)];
|
||||
const body = newMPollBody(votes, ends, null, false);
|
||||
expect(body.html()).toMatchSnapshot();
|
||||
const { container } = newMPollBody(votes, ends, undefined, false);
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -941,7 +955,7 @@ function newEndRelations(relationEvents: Array<MatrixEvent>): Relations {
|
|||
}
|
||||
|
||||
function newRelations(relationEvents: Array<MatrixEvent>, eventType: string): Relations {
|
||||
const voteRelations = new Relations("m.reference", eventType, null);
|
||||
const voteRelations = new Relations("m.reference", eventType, mockClient);
|
||||
for (const ev of relationEvents) {
|
||||
voteRelations.addEvent(ev);
|
||||
}
|
||||
|
@ -951,29 +965,27 @@ function newRelations(relationEvents: Array<MatrixEvent>, eventType: string): Re
|
|||
function newMPollBody(
|
||||
relationEvents: Array<MatrixEvent>,
|
||||
endEvents: Array<MatrixEvent> = [],
|
||||
answers?: POLL_ANSWER[],
|
||||
answers?: PollAnswer[],
|
||||
disclosed = true,
|
||||
): ReactWrapper {
|
||||
): RenderResult {
|
||||
const mxEvent = new MatrixEvent({
|
||||
type: M_POLL_START.name,
|
||||
event_id: "$mypoll",
|
||||
room_id: "#myroom:example.com",
|
||||
content: newPollStart(answers, null, disclosed),
|
||||
content: newPollStart(answers, undefined, disclosed),
|
||||
});
|
||||
return newMPollBodyFromEvent(mxEvent, relationEvents, endEvents);
|
||||
}
|
||||
|
||||
function newMPollBodyFromEvent(
|
||||
function getMPollBodyPropsFromEvent(
|
||||
mxEvent: MatrixEvent,
|
||||
relationEvents: Array<MatrixEvent>,
|
||||
endEvents: Array<MatrixEvent> = [],
|
||||
): ReactWrapper {
|
||||
): IBodyProps {
|
||||
const voteRelations = newVoteRelations(relationEvents);
|
||||
const endRelations = newEndRelations(endEvents);
|
||||
return mount(
|
||||
<MPollBody
|
||||
mxEvent={mxEvent}
|
||||
getRelationsForEvent={(eventId: string, relationType: string, eventType: string) => {
|
||||
|
||||
const getRelationsForEvent = (eventId: string, relationType: string, eventType: string) => {
|
||||
expect(eventId).toBe("$mypoll");
|
||||
expect(relationType).toBe("m.reference");
|
||||
if (M_POLL_RESPONSE.matches(eventType)) {
|
||||
|
@ -983,57 +995,63 @@ function newMPollBodyFromEvent(
|
|||
} else {
|
||||
fail("Unexpected eventType: " + eventType);
|
||||
}
|
||||
}}
|
||||
};
|
||||
|
||||
return {
|
||||
mxEvent,
|
||||
getRelationsForEvent,
|
||||
// We don't use any of these props, but they're required.
|
||||
highlightLink="unused"
|
||||
highlights={[]}
|
||||
mediaEventHelper={null}
|
||||
onHeightChanged={() => {}}
|
||||
onMessageAllowed={() => {}}
|
||||
permalinkCreator={null}
|
||||
/>,
|
||||
{
|
||||
wrappingComponent: MatrixClientContext.Provider,
|
||||
wrappingComponentProps: {
|
||||
value: mockClient,
|
||||
},
|
||||
},
|
||||
);
|
||||
highlightLink: "unused",
|
||||
highlights: [],
|
||||
mediaEventHelper: {} as unknown as MediaEventHelper,
|
||||
onHeightChanged: () => {},
|
||||
onMessageAllowed: () => {},
|
||||
permalinkCreator: {} as unknown as RoomPermalinkCreator,
|
||||
};
|
||||
}
|
||||
|
||||
function clickRadio(wrapper: ReactWrapper, value: string) {
|
||||
const div = wrapper.find(`StyledRadioButton[value="${value}"]`);
|
||||
expect(div).toHaveLength(1);
|
||||
div.simulate("click");
|
||||
function renderMPollBodyWithWrapper(props: IBodyProps): RenderResult {
|
||||
return render(<MPollBody {...props} />, {
|
||||
wrapper: ({ children }) => (
|
||||
<MatrixClientContext.Provider value={mockClient}>{children}</MatrixClientContext.Provider>
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
function clickEndedOption(wrapper: ReactWrapper, value: string) {
|
||||
const div = wrapper.find(`div[data-value="${value}"]`);
|
||||
expect(div).toHaveLength(1);
|
||||
div.simulate("click");
|
||||
function newMPollBodyFromEvent(
|
||||
mxEvent: MatrixEvent,
|
||||
relationEvents: Array<MatrixEvent>,
|
||||
endEvents: Array<MatrixEvent> = [],
|
||||
): RenderResult {
|
||||
const props = getMPollBodyPropsFromEvent(mxEvent, relationEvents, endEvents);
|
||||
return renderMPollBodyWithWrapper(props);
|
||||
}
|
||||
|
||||
function voteButton(wrapper: ReactWrapper, value: string): ReactWrapper {
|
||||
return wrapper.find(`div.mx_MPollBody_option`).findWhere((w) => w.key() === value);
|
||||
function clickOption({ getByTestId }: RenderResult, value: string) {
|
||||
fireEvent.click(getByTestId(`pollOption-${value}`));
|
||||
}
|
||||
|
||||
function votesCount(wrapper: ReactWrapper, value: string): string {
|
||||
return wrapper.find(`StyledRadioButton[value="${value}"] .mx_MPollBody_optionVoteCount`).text();
|
||||
function voteButton({ getByTestId }: RenderResult, value: string): Element {
|
||||
return getByTestId(`pollOption-${value}`);
|
||||
}
|
||||
|
||||
function endedVoteChecked(wrapper: ReactWrapper, value: string): boolean {
|
||||
return endedVoteDiv(wrapper, value).closest(".mx_MPollBody_option").hasClass("mx_MPollBody_option_checked");
|
||||
function votesCount({ getByTestId }: RenderResult, value: string): string {
|
||||
return getByTestId(`pollOption-${value}`).querySelector(".mx_MPollBody_optionVoteCount")!.innerHTML;
|
||||
}
|
||||
|
||||
function endedVoteDiv(wrapper: ReactWrapper, value: string): ReactWrapper {
|
||||
return wrapper.find(`div[data-value="${value}"]`);
|
||||
function endedVoteChecked({ getByTestId }: RenderResult, value: string): boolean {
|
||||
return getByTestId(`pollOption-${value}`).className.includes("mx_MPollBody_option_checked");
|
||||
}
|
||||
|
||||
function endedVotesCount(wrapper: ReactWrapper, value: string): string {
|
||||
return wrapper.find(`div[data-value="${value}"] .mx_MPollBody_optionVoteCount`).text();
|
||||
function endedVoteDiv({ getByTestId }: RenderResult, value: string): Element {
|
||||
return getByTestId(`pollOption-${value}`).firstElementChild!;
|
||||
}
|
||||
|
||||
function newPollStart(answers?: POLL_ANSWER[], question?: string, disclosed = true): M_POLL_START_EVENT_CONTENT {
|
||||
function endedVotesCount(renderResult: RenderResult, value: string): string {
|
||||
return votesCount(renderResult, value);
|
||||
}
|
||||
|
||||
function newPollStart(answers?: PollAnswer[], question?: string, disclosed = true): PollStartEventContent {
|
||||
if (!answers) {
|
||||
answers = [
|
||||
{ id: "pizza", [M_TEXT.name]: "Pizza" },
|
||||
|
@ -1047,7 +1065,7 @@ function newPollStart(answers?: POLL_ANSWER[], question?: string, disclosed = tr
|
|||
question = "What should we order for the party?";
|
||||
}
|
||||
|
||||
const answersFallback = answers.map((a, i) => `${i + 1}. ${a[M_TEXT.name]}`).join("\n");
|
||||
const answersFallback = answers.map((a, i) => `${i + 1}. ${M_TEXT.findIn<string>(a)}`).join("\n");
|
||||
|
||||
const fallback = `${question}\n${answersFallback}`;
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -23,14 +23,10 @@ import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
|||
import { EventType, RelationType, MsgType } from "matrix-js-sdk/src/@types/event";
|
||||
import { RoomStateEvent } from "matrix-js-sdk/src/models/room-state";
|
||||
import { IEvent, Room, EventTimelineSet, IMinimalEvent } from "matrix-js-sdk/src/matrix";
|
||||
import {
|
||||
M_POLL_RESPONSE,
|
||||
M_POLL_END,
|
||||
M_POLL_KIND_DISCLOSED,
|
||||
PollStartEvent,
|
||||
PollResponseEvent,
|
||||
PollEndEvent,
|
||||
} from "matrix-events-sdk";
|
||||
import { M_POLL_RESPONSE, M_POLL_END, M_POLL_KIND_DISCLOSED } from "matrix-js-sdk/src/@types/polls";
|
||||
import { PollStartEvent } from "matrix-js-sdk/src/extensible_events_v1/PollStartEvent";
|
||||
import { PollResponseEvent } from "matrix-js-sdk/src/extensible_events_v1/PollResponseEvent";
|
||||
import { PollEndEvent } from "matrix-js-sdk/src/extensible_events_v1/PollEndEvent";
|
||||
|
||||
import { stubClient, mkStubRoom, mkEvent, mkMessage } from "../../../test-utils";
|
||||
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
|
||||
|
|
|
@ -17,90 +17,118 @@ limitations under the License.
|
|||
import React from "react";
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { AllActionStates, FormattingFunctions } from "@matrix-org/matrix-wysiwyg";
|
||||
import { ActionState, ActionTypes, AllActionStates, FormattingFunctions } from "@matrix-org/matrix-wysiwyg";
|
||||
|
||||
import { FormattingButtons } from "../../../../../../src/components/views/rooms/wysiwyg_composer/components/FormattingButtons";
|
||||
import * as LinkModal from "../../../../../../src/components/views/rooms/wysiwyg_composer/components/LinkModal";
|
||||
|
||||
describe("FormattingButtons", () => {
|
||||
const wysiwyg = {
|
||||
const mockWysiwyg = {
|
||||
bold: jest.fn(),
|
||||
italic: jest.fn(),
|
||||
underline: jest.fn(),
|
||||
strikeThrough: jest.fn(),
|
||||
inlineCode: jest.fn(),
|
||||
link: jest.fn(),
|
||||
orderedList: jest.fn(),
|
||||
unorderedList: jest.fn(),
|
||||
} as unknown as FormattingFunctions;
|
||||
|
||||
const actionStates = {
|
||||
bold: "reversed",
|
||||
italic: "reversed",
|
||||
underline: "enabled",
|
||||
strikeThrough: "enabled",
|
||||
inlineCode: "enabled",
|
||||
link: "enabled",
|
||||
} as AllActionStates;
|
||||
const openLinkModalSpy = jest.spyOn(LinkModal, "openLinkModal");
|
||||
|
||||
const testCases: Record<
|
||||
Exclude<ActionTypes, "undo" | "redo" | "clear">,
|
||||
{ label: string; mockFormatFn: jest.Func | jest.SpyInstance }
|
||||
> = {
|
||||
bold: { label: "Bold", mockFormatFn: mockWysiwyg.bold },
|
||||
italic: { label: "Italic", mockFormatFn: mockWysiwyg.italic },
|
||||
underline: { label: "Underline", mockFormatFn: mockWysiwyg.underline },
|
||||
strikeThrough: { label: "Strikethrough", mockFormatFn: mockWysiwyg.strikeThrough },
|
||||
inlineCode: { label: "Code", mockFormatFn: mockWysiwyg.inlineCode },
|
||||
link: { label: "Link", mockFormatFn: openLinkModalSpy },
|
||||
orderedList: { label: "Numbered list", mockFormatFn: mockWysiwyg.orderedList },
|
||||
unorderedList: { label: "Bulleted list", mockFormatFn: mockWysiwyg.unorderedList },
|
||||
};
|
||||
|
||||
const createActionStates = (state: ActionState): AllActionStates => {
|
||||
return Object.fromEntries(Object.keys(testCases).map((testKey) => [testKey, state])) as AllActionStates;
|
||||
};
|
||||
|
||||
const defaultActionStates = createActionStates("enabled");
|
||||
|
||||
const renderComponent = (props = {}) => {
|
||||
return render(<FormattingButtons composer={mockWysiwyg} actionStates={defaultActionStates} {...props} />);
|
||||
};
|
||||
|
||||
const classes = {
|
||||
active: "mx_FormattingButtons_active",
|
||||
hover: "mx_FormattingButtons_Button_hover",
|
||||
};
|
||||
|
||||
describe("FormattingButtons", () => {
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
it("Should have the correspond CSS classes", () => {
|
||||
// When
|
||||
render(<FormattingButtons composer={wysiwyg} actionStates={actionStates} />);
|
||||
it("Each button should not have active class when enabled", () => {
|
||||
renderComponent();
|
||||
|
||||
// Then
|
||||
expect(screen.getByLabelText("Bold")).toHaveClass("mx_FormattingButtons_active");
|
||||
expect(screen.getByLabelText("Italic")).toHaveClass("mx_FormattingButtons_active");
|
||||
expect(screen.getByLabelText("Underline")).not.toHaveClass("mx_FormattingButtons_active");
|
||||
expect(screen.getByLabelText("Strikethrough")).not.toHaveClass("mx_FormattingButtons_active");
|
||||
expect(screen.getByLabelText("Code")).not.toHaveClass("mx_FormattingButtons_active");
|
||||
expect(screen.getByLabelText("Link")).not.toHaveClass("mx_FormattingButtons_active");
|
||||
});
|
||||
|
||||
it("Should call wysiwyg function on button click", () => {
|
||||
// When
|
||||
const spy = jest.spyOn(LinkModal, "openLinkModal");
|
||||
render(<FormattingButtons composer={wysiwyg} actionStates={actionStates} />);
|
||||
screen.getByLabelText("Bold").click();
|
||||
screen.getByLabelText("Italic").click();
|
||||
screen.getByLabelText("Underline").click();
|
||||
screen.getByLabelText("Strikethrough").click();
|
||||
screen.getByLabelText("Code").click();
|
||||
screen.getByLabelText("Link").click();
|
||||
|
||||
// Then
|
||||
expect(wysiwyg.bold).toHaveBeenCalledTimes(1);
|
||||
expect(wysiwyg.italic).toHaveBeenCalledTimes(1);
|
||||
expect(wysiwyg.underline).toHaveBeenCalledTimes(1);
|
||||
expect(wysiwyg.strikeThrough).toHaveBeenCalledTimes(1);
|
||||
expect(wysiwyg.inlineCode).toHaveBeenCalledTimes(1);
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("Should display the tooltip on mouse over", async () => {
|
||||
// When
|
||||
const user = userEvent.setup();
|
||||
render(<FormattingButtons composer={wysiwyg} actionStates={actionStates} />);
|
||||
await user.hover(screen.getByLabelText("Bold"));
|
||||
|
||||
// Then
|
||||
expect(await screen.findByText("Bold")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("Should not have hover style when active", async () => {
|
||||
// When
|
||||
const user = userEvent.setup();
|
||||
render(<FormattingButtons composer={wysiwyg} actionStates={actionStates} />);
|
||||
await user.hover(screen.getByLabelText("Bold"));
|
||||
|
||||
// Then
|
||||
expect(screen.getByLabelText("Bold")).not.toHaveClass("mx_FormattingButtons_Button_hover");
|
||||
|
||||
// When
|
||||
await user.hover(screen.getByLabelText("Underline"));
|
||||
|
||||
// Then
|
||||
expect(screen.getByLabelText("Underline")).toHaveClass("mx_FormattingButtons_Button_hover");
|
||||
Object.values(testCases).forEach(({ label }) => {
|
||||
expect(screen.getByLabelText(label)).not.toHaveClass(classes.active);
|
||||
});
|
||||
});
|
||||
|
||||
it("Each button should have active class when reversed", () => {
|
||||
const reversedActionStates = createActionStates("reversed");
|
||||
renderComponent({ actionStates: reversedActionStates });
|
||||
|
||||
Object.values(testCases).forEach((testCase) => {
|
||||
const { label } = testCase;
|
||||
expect(screen.getByLabelText(label)).toHaveClass(classes.active);
|
||||
});
|
||||
});
|
||||
|
||||
it("Should call wysiwyg function on button click", async () => {
|
||||
renderComponent();
|
||||
|
||||
for (const testCase of Object.values(testCases)) {
|
||||
const { label, mockFormatFn } = testCase;
|
||||
|
||||
screen.getByLabelText(label).click();
|
||||
expect(mockFormatFn).toHaveBeenCalledTimes(1);
|
||||
}
|
||||
});
|
||||
|
||||
it("Each button should display the tooltip on mouse over", async () => {
|
||||
renderComponent();
|
||||
|
||||
for (const testCase of Object.values(testCases)) {
|
||||
const { label } = testCase;
|
||||
|
||||
await userEvent.hover(screen.getByLabelText(label));
|
||||
expect(await screen.findByText(label)).toBeTruthy();
|
||||
}
|
||||
});
|
||||
|
||||
it("Each button should have hover style when hovered and enabled", async () => {
|
||||
renderComponent();
|
||||
|
||||
for (const testCase of Object.values(testCases)) {
|
||||
const { label } = testCase;
|
||||
|
||||
await userEvent.hover(screen.getByLabelText(label));
|
||||
expect(screen.getByLabelText(label)).toHaveClass("mx_FormattingButtons_Button_hover");
|
||||
}
|
||||
});
|
||||
|
||||
it("Each button should not have hover style when hovered and reversed", async () => {
|
||||
const reversedActionStates = createActionStates("reversed");
|
||||
renderComponent({ actionStates: reversedActionStates });
|
||||
|
||||
for (const testCase of Object.values(testCases)) {
|
||||
const { label } = testCase;
|
||||
|
||||
await userEvent.hover(screen.getByLabelText(label));
|
||||
expect(screen.getByLabelText(label)).not.toHaveClass("mx_FormattingButtons_Button_hover");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -15,9 +15,10 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import { MatrixEvent } from "matrix-js-sdk/src/matrix";
|
||||
import { M_TEXT, M_POLL_START, POLL_ANSWER, M_POLL_KIND_DISCLOSED } from "matrix-events-sdk";
|
||||
import { M_POLL_START, PollAnswer, M_POLL_KIND_DISCLOSED } from "matrix-js-sdk/src/@types/polls";
|
||||
import { M_TEXT } from "matrix-js-sdk/src/@types/extensible_events";
|
||||
|
||||
export const makePollStartEvent = (question: string, sender: string, answers?: POLL_ANSWER[]): MatrixEvent => {
|
||||
export const makePollStartEvent = (question: string, sender: string, answers?: PollAnswer[]): MatrixEvent => {
|
||||
if (!answers) {
|
||||
answers = [
|
||||
{ id: "socks", [M_TEXT.name]: "Socks" },
|
||||
|
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { TEXT_NODE_TYPE } from "matrix-js-sdk/src/@types/extensible_events";
|
||||
import { M_TEXT } from "matrix-js-sdk/src/@types/extensible_events";
|
||||
import {
|
||||
ILocationContent,
|
||||
LocationAssetType,
|
||||
|
@ -38,7 +38,7 @@ describe("isSelfLocation", () => {
|
|||
msgtype: "m.location",
|
||||
geo_uri: "",
|
||||
[M_LOCATION.name]: { uri: "" },
|
||||
[TEXT_NODE_TYPE.name]: "",
|
||||
[M_TEXT.name]: "",
|
||||
[M_TIMESTAMP.name]: 0,
|
||||
// Note: no m.asset!
|
||||
};
|
||||
|
@ -51,7 +51,7 @@ describe("isSelfLocation", () => {
|
|||
msgtype: "m.location",
|
||||
geo_uri: "",
|
||||
[M_LOCATION.name]: { uri: "" },
|
||||
[TEXT_NODE_TYPE.name]: "",
|
||||
[M_TEXT.name]: "",
|
||||
[M_TIMESTAMP.name]: 0,
|
||||
[M_ASSET.name]: {
|
||||
// Note: no type!
|
||||
|
|
Loading…
Reference in a new issue