diff --git a/src/components/views/elements/ReplyChain.tsx b/src/components/views/elements/ReplyChain.tsx index d81ff992ee..f082947162 100644 --- a/src/components/views/elements/ReplyChain.tsx +++ b/src/components/views/elements/ReplyChain.tsx @@ -106,7 +106,13 @@ export default class ReplyChain extends React.Component { if (el) { const code: HTMLElement | null = el.querySelector("code"); const isCodeEllipsisShown = code ? code.offsetHeight >= SHOW_EXPAND_QUOTE_PIXELS : false; - const isElipsisShown = el.offsetHeight >= SHOW_EXPAND_QUOTE_PIXELS || isCodeEllipsisShown; + const isElipsisShown = + isCodeEllipsisShown || + el.offsetHeight >= SHOW_EXPAND_QUOTE_PIXELS || + // Check whether the body fits into it's scroll container + el.clientHeight !== el.scrollHeight || + // Do the same for its children as the scroll container may be on them instead + [...el.children].some((child) => child.clientHeight !== child.scrollHeight); if (isElipsisShown) { this.props.setQuoteExpanded(false); } diff --git a/test/unit-tests/components/views/elements/ReplyChain-test.tsx b/test/unit-tests/components/views/elements/ReplyChain-test.tsx index 38f5684eb7..4f750869c6 100644 --- a/test/unit-tests/components/views/elements/ReplyChain-test.tsx +++ b/test/unit-tests/components/views/elements/ReplyChain-test.tsx @@ -1,81 +1,62 @@ /* -Copyright 2024 New Vector Ltd. -Copyright 2021 The Matrix.org Foundation C.I.C. +Copyright 2025 Element Creations Ltd. SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial Please see LICENSE files in the repository root for full details. */ -import * as testUtils from "../../../../test-utils"; -import { getParentEventId } from "../../../../../src/utils/Reply"; +import React from "react"; +import { render, waitFor } from "jest-matrix-react"; + +import ReplyChain from "../../../../../src/components/views/elements/ReplyChain.tsx"; +import { mkEvent, stubClient, withClientContextRenderOptions } from "../../../../test-utils"; describe("ReplyChain", () => { - describe("getParentEventId", () => { - it("retrieves relation reply from unedited event", () => { - const originalEventWithRelation = testUtils.mkEvent({ - event: true, - type: "m.room.message", - content: { - "msgtype": "m.text", - "body": "> Reply to this message\n\n foo", - "m.relates_to": { - "m.in_reply_to": { - event_id: "$qkjmFBTEc0VvfVyzq1CJuh1QZi_xDIgNEFjZ4Pq34og", - }, - }, - }, - user: "some_other_user", - room: "room_id", - }); + it("should call setQuoteExpanded if chain is longer than 2 lines", async () => { + // Jest/JSDOM won't set clientHeight/scrollHeight for us so we have to synthesise it + jest.spyOn(Element.prototype, "clientHeight", "get").mockReturnValue(100); + jest.spyOn(Element.prototype, "scrollHeight", "get").mockReturnValue(150); - expect(getParentEventId(originalEventWithRelation)).toStrictEqual( - "$qkjmFBTEc0VvfVyzq1CJuh1QZi_xDIgNEFjZ4Pq34og", - ); + const cli = stubClient(); + const { room_id: roomId } = await cli.createRoom({}); + const room = cli.getRoom(roomId)!; + + const targetEv = mkEvent({ + event: true, + type: "m.room.message", + user: cli.getUserId()!, + room: roomId, + id: "$event1", + content: { + body: "A\nB\nC", + msgtype: "m.text", + }, }); + jest.spyOn(room, "findEventById").mockReturnValue(targetEv); - it("retrieves relation reply from original event when edited", () => { - const originalEventWithRelation = testUtils.mkEvent({ - event: true, - type: "m.room.message", - content: { - "msgtype": "m.text", - "body": "> Reply to this message\n\n foo", - "m.relates_to": { - "m.in_reply_to": { - event_id: "$qkjmFBTEc0VvfVyzq1CJuh1QZi_xDIgNEFjZ4Pq34og", - }, + const parentEv = mkEvent({ + event: true, + type: "m.room.message", + user: cli.getUserId()!, + room: roomId, + id: "$event2", + content: { + "body": "Reply", + "msgtype": "m.text", + "m.relates_to": { + "m.in_reply_to": { + event_id: "$event1", }, }, - user: "some_other_user", - room: "room_id", - }); - - const editEvent = testUtils.mkEvent({ - event: true, - type: "m.room.message", - content: { - "msgtype": "m.text", - "body": "> Reply to this message\n\n * foo bar", - "m.new_content": { - msgtype: "m.text", - body: "foo bar", - }, - "m.relates_to": { - rel_type: "m.replace", - event_id: originalEventWithRelation.getId(), - }, - }, - user: "some_other_user", - room: "room_id", - }); - - // The edit replaces the original event - originalEventWithRelation.makeReplaced(editEvent); - - // The relation should be pulled from the original event - expect(getParentEventId(originalEventWithRelation)).toStrictEqual( - "$qkjmFBTEc0VvfVyzq1CJuh1QZi_xDIgNEFjZ4Pq34og", - ); + }, }); + const setQuoteExpanded = jest.fn(); + const { asFragment } = render( + , + withClientContextRenderOptions(cli), + ); + + await waitFor(() => expect(setQuoteExpanded).toHaveBeenCalledWith(false)); + expect(asFragment()).toMatchSnapshot(); }); }); diff --git a/test/unit-tests/components/views/elements/__snapshots__/ReplyChain-test.tsx.snap b/test/unit-tests/components/views/elements/__snapshots__/ReplyChain-test.tsx.snap new file mode 100644 index 0000000000..b22e49ae12 --- /dev/null +++ b/test/unit-tests/components/views/elements/__snapshots__/ReplyChain-test.tsx.snap @@ -0,0 +1,62 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ReplyChain should call setQuoteExpanded if chain is longer than 2 lines 1`] = ` + +
+
+ +
+ +`; diff --git a/test/unit-tests/Reply-test.ts b/test/unit-tests/utils/Reply-test.ts similarity index 66% rename from test/unit-tests/Reply-test.ts rename to test/unit-tests/utils/Reply-test.ts index be354632d4..6755480a7a 100644 --- a/test/unit-tests/Reply-test.ts +++ b/test/unit-tests/utils/Reply-test.ts @@ -8,8 +8,8 @@ Please see LICENSE files in the repository root for full details. import { Room } from "matrix-js-sdk/src/matrix"; -import { getParentEventId, shouldDisplayReply, stripHTMLReply, stripPlainReply } from "../../src/utils/Reply"; -import { mkEvent, stubClient } from "../test-utils"; +import { getParentEventId, shouldDisplayReply, stripHTMLReply, stripPlainReply } from "../../../src/utils/Reply"; +import { mkEvent, stubClient } from "../../test-utils"; // don't litter test console with logs jest.mock("matrix-js-sdk/src/logger"); @@ -59,6 +59,51 @@ describe("Reply", () => { expect(getParentEventId(event)).toBe("$event1"); }); + + it("returns id of relation reply from original event when edited", () => { + const originalEventWithRelation = mkEvent({ + event: true, + type: "m.room.message", + content: { + "msgtype": "m.text", + "body": "> Reply to this message\n\n foo", + "m.relates_to": { + "m.in_reply_to": { + event_id: "$qkjmFBTEc0VvfVyzq1CJuh1QZi_xDIgNEFjZ4Pq34og", + }, + }, + }, + user: "some_other_user", + room: "room_id", + }); + + const editEvent = mkEvent({ + event: true, + type: "m.room.message", + content: { + "msgtype": "m.text", + "body": "> Reply to this message\n\n * foo bar", + "m.new_content": { + msgtype: "m.text", + body: "foo bar", + }, + "m.relates_to": { + rel_type: "m.replace", + event_id: originalEventWithRelation.getId(), + }, + }, + user: "some_other_user", + room: "room_id", + }); + + // The edit replaces the original event + originalEventWithRelation.makeReplaced(editEvent); + + // The relation should be pulled from the original event + expect(getParentEventId(originalEventWithRelation)).toStrictEqual( + "$qkjmFBTEc0VvfVyzq1CJuh1QZi_xDIgNEFjZ4Pq34og", + ); + }); }); describe("stripPlainReply", () => {