Fix expand/collapse reply preview not showing in some cases (#31639)

* Fix expand/collapse reply preview not showing in some cases

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Move tests to appropriate place

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Add comments

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
Michael Telatynski 2026-01-06 11:57:23 +00:00 committed by GitHub
parent c078a596f9
commit ddad82075a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 162 additions and 68 deletions

View File

@ -106,7 +106,13 @@ export default class ReplyChain extends React.Component<IProps, IState> {
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);
}

View File

@ -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(
<ReplyChain parentEv={parentEv} setQuoteExpanded={setQuoteExpanded} />,
withClientContextRenderOptions(cli),
);
await waitFor(() => expect(setQuoteExpanded).toHaveBeenCalledWith(false));
expect(asFragment()).toMatchSnapshot();
});
});

View File

@ -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`] = `
<DocumentFragment>
<div
class="mx_ReplyChain_wrapper"
>
<div />
<div>
<blockquote
class="mx_ReplyChain mx_ReplyChain_color2"
>
<div
class="mx_ReplyTile"
>
<a
href="#"
>
<div
class="mx_ReplyTile_sender"
>
<span
class="_avatar_zysgz_8 mx_BaseAvatar _avatar-imageless_zysgz_55"
data-color="2"
data-testid="avatar-img"
data-type="round"
role="presentation"
style="--cpd-avatar-size: 16px;"
title="@userId:matrix.org"
>
u
</span>
<div
class="mx_DisambiguatedProfile"
>
<span
class="mx_Username_color2 mx_DisambiguatedProfile_displayName"
dir="auto"
>
@userId:matrix.org
</span>
</div>
</div>
<div
class="mx_MTextBody mx_EventTile_content"
>
<div
class="mx_EventTile_body translate"
dir="auto"
>
A
B
C
</div>
</div>
</a>
</div>
</blockquote>
</div>
</div>
</DocumentFragment>
`;

View File

@ -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", () => {