diff --git a/cypress/e2e/editing/editing.spec.ts b/cypress/e2e/editing/editing.spec.ts index ae334ecf92..e056329a7b 100644 --- a/cypress/e2e/editing/editing.spec.ts +++ b/cypress/e2e/editing/editing.spec.ts @@ -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++)); } }); diff --git a/cypress/e2e/polls/polls.spec.ts b/cypress/e2e/polls/polls.spec.ts index f73524965c..51d169d61b 100644 --- a/cypress/e2e/polls/polls.spec.ts +++ b/cypress/e2e/polls/polls.spec.ts @@ -16,8 +16,6 @@ limitations under the License. /// -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], + }, + }); }); }); }; diff --git a/res/css/views/dialogs/_UserSettingsDialog.pcss b/res/css/views/dialogs/_UserSettingsDialog.pcss index 118c057b83..41d39f8b79 100644 --- a/res/css/views/dialogs/_UserSettingsDialog.pcss +++ b/res/css/views/dialogs/_UserSettingsDialog.pcss @@ -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"); } diff --git a/res/css/views/rooms/_EventTile.pcss b/res/css/views/rooms/_EventTile.pcss index c7e857fc9c..57bf2ce3c8 100644 --- a/res/css/views/rooms/_EventTile.pcss +++ b/res/css/views/rooms/_EventTile.pcss @@ -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; + } } } diff --git a/res/css/views/rooms/wysiwyg_composer/components/_Editor.pcss b/res/css/views/rooms/wysiwyg_composer/components/_Editor.pcss index d48476cfd7..7ff3f11a50 100644 --- a/res/css/views/rooms/wysiwyg_composer/components/_Editor.pcss +++ b/res/css/views/rooms/wysiwyg_composer/components/_Editor.pcss @@ -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 { diff --git a/res/img/element-icons/room/composer/bulleted_list.svg b/res/img/element-icons/room/composer/bulleted_list.svg new file mode 100644 index 0000000000..828bb8ab03 --- /dev/null +++ b/res/img/element-icons/room/composer/bulleted_list.svg @@ -0,0 +1,3 @@ + + + diff --git a/res/img/element-icons/room/composer/numbered_list.svg b/res/img/element-icons/room/composer/numbered_list.svg new file mode 100644 index 0000000000..46a5438f3f --- /dev/null +++ b/res/img/element-icons/room/composer/numbered_list.svg @@ -0,0 +1,3 @@ + + + diff --git a/res/img/element-icons/settings/devices.svg b/res/img/element-icons/settings/devices.svg new file mode 100644 index 0000000000..4d2f171993 --- /dev/null +++ b/res/img/element-icons/settings/devices.svg @@ -0,0 +1,5 @@ + + + + diff --git a/src/TextForEvent.tsx b/src/TextForEvent.tsx index ef7d518e74..7f874f8a89 100644 --- a/src/TextForEvent.tsx +++ b/src/TextForEvent.tsx @@ -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"; diff --git a/src/components/structures/ViewSource.tsx b/src/components/structures/ViewSource.tsx index b87194f251..245c1587c5 100644 --- a/src/components/structures/ViewSource.tsx +++ b/src/components/structures/ViewSource.tsx @@ -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 { {_t("Decrypted event source")} - - {stringify(decryptedEventSource)} - + {decryptedEventSource ? ( + + {stringify(decryptedEventSource)} + + ) : ( +
{_t("Decrypted source unavailable")}
+ )}
diff --git a/src/components/views/context_menus/MessageContextMenu.tsx b/src/components/views/context_menus/MessageContextMenu.tsx index 74d6232c2f..3d38fc5a70 100644 --- a/src/components/views/context_menus/MessageContextMenu.tsx +++ b/src/components/views/context_menus/MessageContextMenu.tsx @@ -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"; diff --git a/src/components/views/dialogs/EndPollDialog.tsx b/src/components/views/dialogs/EndPollDialog.tsx index dfba9cb075..946f209d31 100644 --- a/src/components/views/dialogs/EndPollDialog.tsx +++ b/src/components/views/dialogs/EndPollDialog.tsx @@ -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"; diff --git a/src/components/views/dialogs/UserSettingsDialog.tsx b/src/components/views/dialogs/UserSettingsDialog.tsx index a7877072b3..7b33ca58da 100644 --- a/src/components/views/dialogs/UserSettingsDialog.tsx +++ b/src/components/views/dialogs/UserSettingsDialog.tsx @@ -164,7 +164,7 @@ export default class UserSettingsDialog extends React.Component new Tab( UserTab.SessionManager, _td("Sessions"), - "mx_UserSettingsDialog_securityIcon", + "mx_UserSettingsDialog_sessionsIcon", , // don't track with posthog while under construction undefined, diff --git a/src/components/views/elements/PollCreateDialog.tsx b/src/components/views/elements/PollCreateDialog.tsx index 594feabbda..4cc4231332 100644 --- a/src/components/views/elements/PollCreateDialog.tsx +++ b/src/components/views/elements/PollCreateDialog.tsx @@ -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 +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 { 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 (
diff --git a/src/components/views/messages/MPollBody.tsx b/src/components/views/messages/MPollBody.tsx
index 805368b1b6..a9317957c6 100644
--- a/src/components/views/messages/MPollBody.tsx
+++ b/src/components/views/messages/MPollBody.tsx
@@ -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 {
 
         return (
             
-

+

{poll.question.text} {editedSpan}

@@ -477,7 +471,12 @@ export default class MPollBody extends React.Component { const answerPercent = totalVotes === 0 ? 0 : Math.round((100.0 * answerVotes) / totalVotes); return ( -
this.selectOption(answer.id)}> +
this.selectOption(answer.id)} + > {ended ? ( ) : ( @@ -499,7 +498,9 @@ export default class MPollBody extends React.Component { ); })}
-
{totalText}
+
+ {totalText} +
); } diff --git a/src/components/views/messages/MessageEvent.tsx b/src/components/views/messages/MessageEvent.tsx index b61f871f7f..781afc9ad5 100644 --- a/src/components/views/messages/MessageEvent.tsx +++ b/src/components/views/messages/MessageEvent.tsx @@ -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"; diff --git a/src/components/views/rooms/MessageComposerButtons.tsx b/src/components/views/rooms/MessageComposerButtons.tsx index 6dfb18f524..17522e3be6 100644 --- a/src/components/views/rooms/MessageComposerButtons.tsx +++ b/src/components/views/rooms/MessageComposerButtons.tsx @@ -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"; diff --git a/src/components/views/rooms/PinnedEventTile.tsx b/src/components/views/rooms/PinnedEventTile.tsx index c5064d5f9c..26b7f63c25 100644 --- a/src/components/views/rooms/PinnedEventTile.tsx +++ b/src/components/views/rooms/PinnedEventTile.tsx @@ -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"; diff --git a/src/components/views/rooms/wysiwyg_composer/components/Editor.tsx b/src/components/views/rooms/wysiwyg_composer/components/Editor.tsx index 215d3be84f..43e46572d6 100644 --- a/src/components/views/rooms/wysiwyg_composer/components/Editor.tsx +++ b/src/components/views/rooms/wysiwyg_composer/components/Editor.tsx @@ -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; diff --git a/src/components/views/rooms/wysiwyg_composer/components/FormattingButtons.tsx b/src/components/views/rooms/wysiwyg_composer/components/FormattingButtons.tsx index 1d3f44bee7..7c1601b441 100644 --- a/src/components/views/rooms/wysiwyg_composer/components/FormattingButtons.tsx +++ b/src/components/views/rooms/wysiwyg_composer/components/FormattingButtons.tsx @@ -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={} /> +
-
{_t("Sessions")}
diff --git a/src/events/EventTileFactory.tsx b/src/events/EventTileFactory.tsx index f3cddcbb4c..1d30416f0f 100644 --- a/src/events/EventTileFactory.tsx +++ b/src/events/EventTileFactory.tsx @@ -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"; diff --git a/src/events/forward/getForwardableEvent.ts b/src/events/forward/getForwardableEvent.ts index 7d1782d7ae..2ceaedc108 100644 --- a/src/events/forward/getForwardableEvent.ts +++ b/src/events/forward/getForwardableEvent.ts @@ -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"; diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index c502cb8f40..54272b871c 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -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", diff --git a/src/stores/room-list/MessagePreviewStore.ts b/src/stores/room-list/MessagePreviewStore.ts index cd8ebb6a84..75469ebb0a 100644 --- a/src/stores/room-list/MessagePreviewStore.ts +++ b/src/stores/room-list/MessagePreviewStore.ts @@ -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"; diff --git a/src/stores/room-list/previews/PollStartEventPreview.ts b/src/stores/room-list/previews/PollStartEventPreview.ts index c4c85ca82c..404a808989 100644 --- a/src/stores/room-list/previews/PollStartEventPreview.ts +++ b/src/stores/room-list/previews/PollStartEventPreview.ts @@ -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(); diff --git a/src/utils/EventRenderingUtils.ts b/src/utils/EventRenderingUtils.ts index ad8061973b..3acd95e48a 100644 --- a/src/utils/EventRenderingUtils.ts +++ b/src/utils/EventRenderingUtils.ts @@ -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"; diff --git a/src/utils/EventUtils.ts b/src/utils/EventUtils.ts index 2459a9f745..e80ffb83e2 100644 --- a/src/utils/EventUtils.ts +++ b/src/utils/EventUtils.ts @@ -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"; diff --git a/src/utils/PinningUtils.ts b/src/utils/PinningUtils.ts index 5bc1901db0..8e146e9876 100644 --- a/src/utils/PinningUtils.ts +++ b/src/utils/PinningUtils.ts @@ -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 { /** diff --git a/test/components/structures/ViewSource-test.tsx b/test/components/structures/ViewSource-test.tsx new file mode 100644 index 0000000000..a4bc8b1eca --- /dev/null +++ b/test/components/structures/ViewSource-test.tsx @@ -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 + expect(messageEvent.clearEvent).toBe(undefined); + + expect(() => render( {}} />)).not.toThrow(); + }); +}); diff --git a/test/components/views/context_menus/MessageContextMenu-test.tsx b/test/components/views/context_menus/MessageContextMenu-test.tsx index a0ad320c5b..91ee949879 100644 --- a/test/components/views/context_menus/MessageContextMenu-test.tsx +++ b/test/components/views/context_menus/MessageContextMenu-test.tsx @@ -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"; diff --git a/test/components/views/dialogs/ForwardDialog-test.tsx b/test/components/views/dialogs/ForwardDialog-test.tsx index 9e534a6c88..d997450522 100644 --- a/test/components/views/dialogs/ForwardDialog-test.tsx +++ b/test/components/views/dialogs/ForwardDialog-test.tsx @@ -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, diff --git a/test/components/views/elements/PollCreateDialog-test.tsx b/test/components/views/elements/PollCreateDialog-test.tsx index fdfb4b50e7..2a0087b355 100644 --- a/test/components/views/elements/PollCreateDialog-test.tsx +++ b/test/components/views/elements/PollCreateDialog-test.tsx @@ -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"; diff --git a/test/components/views/elements/SyntaxHighlight-test.tsx b/test/components/views/elements/SyntaxHighlight-test.tsx new file mode 100644 index 0000000000..bdd3e50cf0 --- /dev/null +++ b/test/components/views/elements/SyntaxHighlight-test.tsx @@ -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("", () => { + it("renders", () => { + const { container } = render(console.log("Hello, World!");); + expect(container).toMatchSnapshot(); + }); + + it.each(["json", "javascript", "css"])("uses the provided language", (lang) => { + const mock = jest.spyOn(hljs, "highlight"); + + render(// Hello, World); + + const [_lang, opts] = mock.mock.lastCall!; + expect((opts as HighlightOptions)["language"]).toBe(lang); + }); +}); diff --git a/test/components/views/elements/__snapshots__/SyntaxHighlight-test.tsx.snap b/test/components/views/elements/__snapshots__/SyntaxHighlight-test.tsx.snap new file mode 100644 index 0000000000..e7ad9c057b --- /dev/null +++ b/test/components/views/elements/__snapshots__/SyntaxHighlight-test.tsx.snap @@ -0,0 +1,30 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` renders 1`] = ` +
+
+    
+      
+        console
+      
+      .
+      
+        log
+      
+      (
+      
+        "Hello, World!"
+      
+      );
+    
+  
+
+`; diff --git a/test/components/views/messages/MPollBody-test.tsx b/test/components/views/messages/MPollBody-test.tsx index c3907a61b9..a6f9b5e11c 100644 --- a/test/components/views/messages/MPollBody-test.tsx +++ b/test/components/views/messages/MPollBody-test.tsx @@ -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("

What should we order for the party?

"); + 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( - "

new question" + ' (edited)' + "

", + const { getByTestId, container } = newMPollBodyFromEvent(pollEvent, []); + expect(getByTestId("pollQuestion").innerHTML).toEqual( + 'new question (edited)', ); - 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): Relations { } function newRelations(relationEvents: Array, 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,89 +965,93 @@ function newRelations(relationEvents: Array, eventType: string): Re function newMPollBody( relationEvents: Array, endEvents: Array = [], - 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 getMPollBodyPropsFromEvent( + mxEvent: MatrixEvent, + relationEvents: Array, + endEvents: Array = [], +): IBodyProps { + const voteRelations = newVoteRelations(relationEvents); + const endRelations = newEndRelations(endEvents); + + const getRelationsForEvent = (eventId: string, relationType: string, eventType: string) => { + expect(eventId).toBe("$mypoll"); + expect(relationType).toBe("m.reference"); + if (M_POLL_RESPONSE.matches(eventType)) { + return voteRelations; + } else if (M_POLL_END.matches(eventType)) { + return endRelations; + } else { + fail("Unexpected eventType: " + eventType); + } + }; + + return { + mxEvent, + getRelationsForEvent, + // We don't use any of these props, but they're required. + highlightLink: "unused", + highlights: [], + mediaEventHelper: {} as unknown as MediaEventHelper, + onHeightChanged: () => {}, + onMessageAllowed: () => {}, + permalinkCreator: {} as unknown as RoomPermalinkCreator, + }; +} + +function renderMPollBodyWithWrapper(props: IBodyProps): RenderResult { + return render(, { + wrapper: ({ children }) => ( + {children} + ), + }); +} + function newMPollBodyFromEvent( mxEvent: MatrixEvent, relationEvents: Array, endEvents: Array = [], -): ReactWrapper { - const voteRelations = newVoteRelations(relationEvents); - const endRelations = newEndRelations(endEvents); - return mount( - { - expect(eventId).toBe("$mypoll"); - expect(relationType).toBe("m.reference"); - if (M_POLL_RESPONSE.matches(eventType)) { - return voteRelations; - } else if (M_POLL_END.matches(eventType)) { - return endRelations; - } else { - fail("Unexpected eventType: " + eventType); - } - }} - // 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, - }, - }, - ); +): RenderResult { + const props = getMPollBodyPropsFromEvent(mxEvent, relationEvents, endEvents); + return renderMPollBodyWithWrapper(props); } -function clickRadio(wrapper: ReactWrapper, value: string) { - const div = wrapper.find(`StyledRadioButton[value="${value}"]`); - expect(div).toHaveLength(1); - div.simulate("click"); +function clickOption({ getByTestId }: RenderResult, value: string) { + fireEvent.click(getByTestId(`pollOption-${value}`)); } -function clickEndedOption(wrapper: ReactWrapper, value: string) { - const div = wrapper.find(`div[data-value="${value}"]`); - expect(div).toHaveLength(1); - div.simulate("click"); +function voteButton({ getByTestId }: RenderResult, value: string): Element { + return getByTestId(`pollOption-${value}`); } -function voteButton(wrapper: ReactWrapper, value: string): ReactWrapper { - return wrapper.find(`div.mx_MPollBody_option`).findWhere((w) => w.key() === value); +function votesCount({ getByTestId }: RenderResult, value: string): string { + return getByTestId(`pollOption-${value}`).querySelector(".mx_MPollBody_optionVoteCount")!.innerHTML; } -function votesCount(wrapper: ReactWrapper, value: string): string { - return wrapper.find(`StyledRadioButton[value="${value}"] .mx_MPollBody_optionVoteCount`).text(); +function endedVoteChecked({ getByTestId }: RenderResult, value: string): boolean { + return getByTestId(`pollOption-${value}`).className.includes("mx_MPollBody_option_checked"); } -function endedVoteChecked(wrapper: ReactWrapper, value: string): boolean { - return endedVoteDiv(wrapper, value).closest(".mx_MPollBody_option").hasClass("mx_MPollBody_option_checked"); +function endedVoteDiv({ getByTestId }: RenderResult, value: string): Element { + return getByTestId(`pollOption-${value}`).firstElementChild!; } -function endedVoteDiv(wrapper: ReactWrapper, value: string): ReactWrapper { - return wrapper.find(`div[data-value="${value}"]`); +function endedVotesCount(renderResult: RenderResult, value: string): string { + return votesCount(renderResult, value); } -function endedVotesCount(wrapper: ReactWrapper, value: string): string { - return wrapper.find(`div[data-value="${value}"] .mx_MPollBody_optionVoteCount`).text(); -} - -function newPollStart(answers?: POLL_ANSWER[], question?: string, disclosed = true): M_POLL_START_EVENT_CONTENT { +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(a)}`).join("\n"); const fallback = `${question}\n${answersFallback}`; diff --git a/test/components/views/messages/__snapshots__/MPollBody-test.tsx.snap b/test/components/views/messages/__snapshots__/MPollBody-test.tsx.snap index e4039b82b6..2263527148 100644 --- a/test/components/views/messages/__snapshots__/MPollBody-test.tsx.snap +++ b/test/components/views/messages/__snapshots__/MPollBody-test.tsx.snap @@ -1,2626 +1,1627 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`MPollBody renders a finished poll 1`] = ` - +
-

+

What should we order for the party?

-
+ Pizza +
+
+ 0 votes +
+
+
+
+
+
+
+
+
+
+
+ Poutine +
+
+ 0 votes +
+
+
+
+
+
+
+
+
+
+
+ Italian +
+
+ 2 votes +
+
+
+
+
+
+
+
+
+
+
+ Wings +
+
+ 1 vote +
+
+
+
+
+
+
+
+
+ Final result based on 3 votes +
+
+
+`; + +exports[`MPollBody renders a finished poll with multiple winners 1`] = ` +
+
+

+ What should we order for the party? +

+
+
+
+
+
+ Pizza +
+
+ 2 votes +
+
+
+
+
+
+
+
+
+
+
+ Poutine +
+
+ 0 votes +
+
+
+
+
+
+
+
+
+
+
+ Italian +
+
+ 0 votes +
+
+
+
+
+
+
+
+
+
+
+ Wings +
+
+ 2 votes +
+
+
+
+
+
+
+
+
+ Final result based on 4 votes +
+
+
+`; + +exports[`MPollBody renders a finished poll with no votes 1`] = ` +
+
+

+ What should we order for the party? +

+
+
+
+
+
+ Pizza +
+
+ 0 votes +
+
+
+
+
+
+
+
+
+
+
+ Poutine +
+
+ 0 votes +
+
+
+
+
+
+
+
+
+
+
+ Italian +
+
+ 0 votes +
+
+
+
+
+
+
+
+
+
+
+ Wings +
+
+ 0 votes +
+
+
+
+
+
+
+
+
+ Final result based on 0 votes +
+
+
+`; + +exports[`MPollBody renders a poll that I have not voted in 1`] = ` +
+
+

+ What should we order for the party? +

+
+
+