diff --git a/cypress/e2e/polls/polls.spec.ts b/cypress/e2e/polls/polls.spec.ts index b2537c2cbe..5fe6b204fa 100644 --- a/cypress/e2e/polls/polls.spec.ts +++ b/cypress/e2e/polls/polls.spec.ts @@ -18,6 +18,8 @@ limitations under the License. import { HomeserverInstance } from "../../plugins/utils/homeserver"; import { MatrixClient } from "../../global"; +import { SettingLevel } from "../../../src/settings/SettingLevel"; +import { Layout } from "../../../src/settings/enums/Layout"; import Chainable = Cypress.Chainable; const hidePercyCSS = ".mx_MessageTimestamp, .mx_RoomView_myReadMarker { visibility: hidden !important; }"; @@ -313,6 +315,18 @@ describe("Polls", () => { // and thread view cy.get(".mx_ThreadView .mx_MPollBody_totalVotes").should("contain", "2 votes cast"); + // Take snapshots of poll on ThreadView + cy.setSettingValue("layout", null, SettingLevel.DEVICE, Layout.Bubble); + cy.get(".mx_ThreadView .mx_EventTile[data-layout='bubble']").should("be.visible"); + cy.get(".mx_ThreadView").percySnapshotElement("ThreadView with a poll on bubble layout", { + percyCSS: hidePercyCSS, + }); + cy.setSettingValue("layout", null, SettingLevel.DEVICE, Layout.Group); + cy.get(".mx_ThreadView .mx_EventTile[data-layout='group']").should("be.visible"); + cy.get(".mx_ThreadView").percySnapshotElement("ThreadView with a poll on group layout", { + percyCSS: hidePercyCSS, + }); + cy.get(".mx_RoomView_body").within(() => { // vote 'Maybe' in the main timeline poll getPollOption(pollId, pollParams.options[2]).click("topLeft"); diff --git a/cypress/e2e/threads/threads.spec.ts b/cypress/e2e/threads/threads.spec.ts index c1af15d2b0..75585c888b 100644 --- a/cypress/e2e/threads/threads.spec.ts +++ b/cypress/e2e/threads/threads.spec.ts @@ -20,6 +20,7 @@ import { HomeserverInstance } from "../../plugins/utils/homeserver"; import { MatrixClient } from "../../global"; import { SettingLevel } from "../../../src/settings/SettingLevel"; import { Layout } from "../../../src/settings/enums/Layout"; +import Chainable = Cypress.Chainable; describe("Threads", () => { let homeserver: HomeserverInstance; @@ -64,6 +65,9 @@ describe("Threads", () => { // --MessageTimestamp-color = #acacac = rgb(172, 172, 172) // See: _MessageTimestamp.pcss const MessageTimestampColor = "rgb(172, 172, 172)"; + const ThreadViewGroupSpacingStart = "56px"; // --ThreadView_group_spacing-start + // Exclude timestamp and read marker from snapshots + const percyCSS = ".mx_MessageTimestamp, .mx_RoomView_myReadMarker { visibility: hidden !important; }"; // User sends message cy.get(".mx_RoomView_body .mx_BasicMessageComposer_input").type("Hello Mr. Bot{enter}"); @@ -90,9 +94,29 @@ describe("Threads", () => { }); // User asserts timeline thread summary visible & clicks it - cy.get(".mx_RoomView_body .mx_ThreadSummary .mx_ThreadSummary_sender").should("contain", "BotBob"); - cy.get(".mx_RoomView_body .mx_ThreadSummary .mx_ThreadSummary_content").should("contain", "Hello there"); - cy.get(".mx_RoomView_body .mx_ThreadSummary").click(); + cy.get(".mx_RoomView_body").within(() => { + cy.get(".mx_ThreadSummary .mx_ThreadSummary_sender").should("contain", "BotBob"); + cy.get(".mx_ThreadSummary .mx_ThreadSummary_content").should("contain", "Hello there"); + cy.get(".mx_ThreadSummary").click(); + }); + + // Wait until the both messages are read + cy.get(".mx_ThreadView .mx_EventTile_last[data-layout=group]").within(() => { + cy.get(".mx_EventTile_line .mx_MTextBody").should("have.text", MessageLong); + cy.get(".mx_ReadReceiptGroup .mx_BaseAvatar_image").should("be.visible"); + + // Make sure the CSS style for spacing is applied to mx_EventTile_line on group/modern layout + cy.get(".mx_EventTile_line").should("have.css", "padding-inline-start", ThreadViewGroupSpacingStart); + }); + + // Take Percy snapshots in group layout and bubble layout (IRC layout is not available on ThreadView) + cy.get(".mx_ThreadView").percySnapshotElement("Initial ThreadView on group layout", { percyCSS }); + cy.setSettingValue("layout", null, SettingLevel.DEVICE, Layout.Bubble); + cy.get(".mx_ThreadView .mx_EventTile[data-layout='bubble']").should("be.visible"); + cy.get(".mx_ThreadView").percySnapshotElement("Initial ThreadView on bubble layout", { percyCSS }); + + // Set the group layout + cy.setSettingValue("layout", null, SettingLevel.DEVICE, Layout.Group); cy.get(".mx_ThreadView .mx_EventTile[data-layout='group'].mx_EventTile_last").within(() => { // Wait until the messages are rendered @@ -130,8 +154,17 @@ describe("Threads", () => { ); // User asserts summary was updated correctly - cy.get(".mx_RoomView_body .mx_ThreadSummary .mx_ThreadSummary_sender").should("contain", "Tom"); - cy.get(".mx_RoomView_body .mx_ThreadSummary .mx_ThreadSummary_content").should("contain", "Test"); + cy.get(".mx_RoomView_body .mx_ThreadSummary").within(() => { + cy.get(".mx_ThreadSummary_sender").should("contain", "Tom"); + cy.get(".mx_ThreadSummary_content").should("contain", "Test"); + }); + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Check reactions and hidden events + //////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + // Enable hidden events to make the event for reaction displayed + cy.setSettingValue("showHiddenEventsInTimeline", null, SettingLevel.DEVICE, true); // User reacts to message instead cy.contains(".mx_ThreadView .mx_EventTile .mx_EventTile_line", "Hello there") @@ -142,6 +175,58 @@ describe("Threads", () => { cy.contains('[role="menuitem"]', "👋").click(); }); + cy.get(".mx_ThreadView").within(() => { + // Make sure the CSS style for spacing is applied to mx_ReactionsRow on group/modern layout + cy.get(".mx_EventTile[data-layout=group] .mx_ReactionsRow").should( + "have.css", + "margin-inline-start", + ThreadViewGroupSpacingStart, + ); + + // Make sure the CSS style for spacing is applied to the hidden event on group/modern layout + cy.get( + ".mx_GenericEventListSummary[data-layout=group] .mx_EventTile_info.mx_EventTile_last " + + ".mx_EventTile_line", + ).should("have.css", "padding-inline-start", ThreadViewGroupSpacingStart); + }); + + // Take Percy snapshot of group layout (IRC layout is not available on ThreadView) + cy.get(".mx_ThreadView").percySnapshotElement("ThreadView with reaction and a hidden event on group layout", { + percyCSS, + }); + + // Enable bubble layout + cy.setSettingValue("layout", null, SettingLevel.DEVICE, Layout.Bubble); + + // Make sure the CSS style for spacing is applied to the hidden event on bubble layout + cy.get( + ".mx_ThreadView .mx_GenericEventListSummary[data-layout=bubble] .mx_EventTile_info.mx_EventTile_last", + ).within(() => { + cy.get(".mx_EventTile_line .mx_EventTile_content") + // 76px: ThreadViewGroupSpacingStart + 14px + 6px + // 14px: avatar width + // See: _EventTile.pcss + .should("have.css", "margin-inline-start", "76px"); + cy.get(".mx_EventTile_line") + // Make sure the margin is NOT applied to mx_EventTile_line + .should("have.css", "margin-inline-start", "0px"); + }); + + // Take Percy snapshot of bubble layout + cy.get(".mx_ThreadView").percySnapshotElement("ThreadView with reaction and a hidden event on bubble layout", { + percyCSS, + }); + + // Disable hidden events + cy.setSettingValue("showHiddenEventsInTimeline", null, SettingLevel.DEVICE, false); + + // Reset to the group layout + cy.setSettingValue("layout", null, SettingLevel.DEVICE, Layout.Group); + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Check redactions + //////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // User redacts their prior response cy.contains(".mx_ThreadView .mx_EventTile .mx_EventTile_line", "Test") .find('[aria-label="Options"]') @@ -153,6 +238,23 @@ describe("Threads", () => { cy.contains(".mx_Dialog_primary", "Remove").click(); }); + // Wait until the response is redacted + cy.get(".mx_ThreadView .mx_EventTile_last .mx_EventTile_receiptSent").should("be.visible"); + + // Take Percy snapshots in group layout and bubble layout (IRC layout is not available on ThreadView) + cy.get(".mx_ThreadView .mx_EventTile[data-layout='group']").should("be.visible"); + cy.get(".mx_ThreadView").percySnapshotElement("ThreadView with redacted messages on group layout", { + percyCSS, + }); + cy.setSettingValue("layout", null, SettingLevel.DEVICE, Layout.Bubble); + cy.get(".mx_ThreadView .mx_EventTile[data-layout='bubble']").should("be.visible"); + cy.get(".mx_ThreadView").percySnapshotElement("ThreadView with redacted messages on bubble layout", { + percyCSS, + }); + + // Set the group layout + cy.setSettingValue("layout", null, SettingLevel.DEVICE, Layout.Group); + // User asserts summary was updated correctly cy.get(".mx_RoomView_body .mx_ThreadSummary .mx_ThreadSummary_sender").should("contain", "BotBob"); cy.get(".mx_RoomView_body .mx_ThreadSummary .mx_ThreadSummary_content").should("contain", "Hello there"); @@ -182,9 +284,15 @@ describe("Threads", () => { cy.get(".mx_EventTile_body").should("contain", "Hello Mr. Bot"); cy.get(".mx_ThreadSummary_content").should("contain", "How are things?"); + // Check the number of the replies + cy.get(".mx_ThreadPanel_replies_amount").should("have.text", "2"); + // Check the colour of timestamp on thread list cy.get(".mx_EventTile_details .mx_MessageTimestamp").should("have.css", "color", MessageTimestampColor); + // Make sure the notification dot is visible + cy.get(".mx_NotificationBadge_visible").should("be.visible"); + // User opens thread via threads list cy.get(".mx_EventTile_line").click(); }); @@ -281,6 +389,80 @@ describe("Threads", () => { cy.get(".mx_ThreadView .mx_MVoiceMessageBody").should("have.length", 1); }); + it("should send location and reply to the location on ThreadView", () => { + // See: location.spec.ts + const selectLocationShareTypeOption = (shareType: string): Chainable => { + return cy.get(`[data-test-id="share-location-option-${shareType}"]`); + }; + const submitShareLocation = (): void => { + cy.get('[data-testid="location-picker-submit-button"]').click(); + }; + + let bot: MatrixClient; + cy.getBot(homeserver, { + displayName: "BotBob", + autoAcceptInvites: false, + }).then((_bot) => { + bot = _bot; + }); + + let roomId: string; + cy.createRoom({}).then((_roomId) => { + roomId = _roomId; + cy.inviteUser(roomId, bot.getUserId()); + bot.joinRoom(roomId); + cy.visit("/#/room/" + roomId); + }); + + // Exclude timestamp, read marker, and mapboxgl-map from snapshots + const percyCSS = + ".mx_MessageTimestamp, .mx_RoomView_myReadMarker, .mapboxgl-map { visibility: hidden !important; }"; + + // User sends message + cy.get(".mx_RoomView_body .mx_BasicMessageComposer_input").type("Hello Mr. Bot{enter}"); + + // Wait for message to send, get its ID and save as @threadId + cy.contains(".mx_RoomView_body .mx_EventTile[data-scroll-tokens]", "Hello Mr. Bot") + .invoke("attr", "data-scroll-tokens") + .as("threadId"); + + // Bot starts thread + cy.get("@threadId").then((threadId) => { + bot.sendMessage(roomId, threadId, { + body: "Hello there", + msgtype: "m.text", + }); + }); + + // User clicks thread summary + cy.get(".mx_RoomView_body .mx_ThreadSummary").click(); + + // User sends location on ThreadView + cy.get(".mx_ThreadView").should("exist"); + cy.openMessageComposerOptions(true).find("[aria-label='Location']").click(); + selectLocationShareTypeOption("Pin").click(); + cy.get("#mx_LocationPicker_map").click("center"); + submitShareLocation(); + cy.get(".mx_ThreadView .mx_EventTile_last .mx_MLocationBody", { timeout: 10000 }).should("exist"); + + // User replies to the location + cy.get(".mx_ThreadView").within(() => { + cy.get(".mx_EventTile_last") + .realHover() + .within(() => { + cy.get("[aria-label='Reply']").click({ force: false }); + }); + + cy.get(".mx_BasicMessageComposer_input").type("Please come here.{enter}"); + + // Wait until the reply is sent + cy.get(".mx_EventTile_last .mx_EventTile_receiptSent").should("be.visible"); + }); + + // Take a snapshot of reply to the shared location + cy.get(".mx_ThreadView").percySnapshotElement("Reply to the location on ThreadView", { percyCSS }); + }); + it("right panel behaves correctly", () => { // Create room let roomId: string; diff --git a/res/css/views/rooms/_EventTile.pcss b/res/css/views/rooms/_EventTile.pcss index 14c7107439..a7bc52d735 100644 --- a/res/css/views/rooms/_EventTile.pcss +++ b/res/css/views/rooms/_EventTile.pcss @@ -1039,19 +1039,6 @@ $left-gutter: 64px; display: none; } - .mx_EventTile_line { - padding-block: var(--BaseCard_EventTile_line-padding-block); - } - - .mx_EventTile_line, - .mx_ReactionsRow { - padding-inline-start: 0; /* Cancel inherited padding value for event message and reactions row */ - } - - .mx_ReactionsRow { - padding-inline-end: 0; - } - /* handling for hidden events (e.g reactions) in the thread view */ &.mx_EventTile_info { .mx_EventTile_avatar { @@ -1078,8 +1065,6 @@ $left-gutter: 64px; .mx_EventTile_content, .mx_RedactedBody { width: auto; - /* 14px: avatar width, 6px: 20px - 14px */ - margin-inline-start: calc(var(--ThreadView_group_spacing-start) + 14px + 6px); font-size: $line-height; } } @@ -1096,12 +1081,29 @@ $left-gutter: 64px; .mx_MessageTimestamp { top: 2px; /* Align with avatar */ } + + &.mx_EventTile_selected .mx_EventTile_line, + .mx_EventTile_line { + .mx_EventTile_content, + .mx_RedactedBody { + /* 14px: avatar width, 6px: 20px - 14px */ + margin-inline-start: calc(14px + 6px); + } + } } &[data-layout="bubble"] { .mx_EventTile_avatar { inset-inline-start: 0; } + + &.mx_EventTile_selected .mx_EventTile_line, + .mx_EventTile_line { + .mx_EventTile_content, + .mx_RedactedBody { + margin-inline-start: calc(var(--ThreadView_group_spacing-start) + 14px + 6px); + } + } } } @@ -1109,59 +1111,38 @@ $left-gutter: 64px; &[data-layout="group"] { padding-block-start: $spacing-16; + .mx_EventTile_line, + .mx_ReactionsRow { + margin-inline-end: var(--ThreadView_group_spacing-end); + } + .mx_EventTile_line { + padding-block: var(--BaseCard_EventTile_line-padding-block); + + /* Add padding to align message text with summary text */ + /* See: .mx_EventTile[data-layout="group"] .mx_EventTile_line */ + padding-inline-start: var(--ThreadView_group_spacing-start); + .mx_EventTile_content { &.mx_EditMessageComposer { padding-inline-start: 0; /* align start of first letter with that of the event body */ } } } + + .mx_ReactionsRow { + /* Align with message text and summary text */ + margin-inline-start: var(--ThreadView_group_spacing-start); + } + + &.mx_EventTile_continuation { + padding-block-start: 0; /* Cancel padding-block-start */ + } } &[data-layout="group"] { width: 100%; - .mx_EventTile_content, - .mx_HiddenBody, - .mx_RedactedBody, - .mx_UnknownBody, - .mx_MPollBody, - .mx_MLocationBody, - .mx_ReplyChain_wrapper, - .mx_ReactionsRow { - margin-inline-start: var(--ThreadView_group_spacing-start); - margin-inline-end: var(--ThreadView_group_spacing-end); - - .mx_EventTile_content, - .mx_HiddenBody, - .mx_RedactedBody, - .mx_MImageBody { - margin: 0; - } - } - - .mx_ReplyChain_wrapper { - .mx_MLocationBody, - .mx_UnknownBody { - /* Error message inside ReplyTile */ - margin-inline: unset; - } - } - - .mx_EventTile_mediaLine { - /* such as MImageBody */ - > div, - > span { - margin-inline-start: var(--ThreadView_group_spacing-start); - margin-inline-end: var(--ThreadView_group_spacing-end); - } - - /* such as MAudioBody and MFileBody */ - > span { - display: block; /* Apply the margin declarations to span element */ - } - } - .mx_EventTile_senderDetails { display: flex; align-items: center;