Fix checkForPreJoinUISI for thread roots (#9803) (#9816)

* Fix blank timeline when thread root is UTD

* add test for pre join uisi checks and thread roots

* ts strict fix

* Update to timeline panel test

Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com>

* Fixes to TimelinePanel-test

Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com>
(cherry picked from commit dd88fbbc3b)

Co-authored-by: Germain <germains@element.io>
This commit is contained in:
ElementRobot 2022-12-21 16:29:40 +00:00 committed by GitHub
parent bf699cdf10
commit e0fe17715a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 71 additions and 6 deletions

View file

@ -1632,7 +1632,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
let i = events.length - 1; let i = events.length - 1;
let userMembership = "leave"; let userMembership = "leave";
for (; i >= 0; i--) { for (; i >= 0; i--) {
const timeline = room.getTimelineForEvent(events[i].getId()); const timeline = this.props.timelineSet.getTimelineForEvent(events[i].getId()!);
if (!timeline) { if (!timeline) {
// Somehow, it seems to be possible for live events to not have // Somehow, it seems to be possible for live events to not have
// a timeline, even though that should not happen. :( // a timeline, even though that should not happen. :(

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import { render, RenderResult } from "@testing-library/react"; import { render, RenderResult, waitFor, screen } from "@testing-library/react";
// eslint-disable-next-line deprecate/import // eslint-disable-next-line deprecate/import
import { mount, ReactWrapper } from "enzyme"; import { mount, ReactWrapper } from "enzyme";
import { MessageEvent } from "matrix-events-sdk"; import { MessageEvent } from "matrix-events-sdk";
@ -27,6 +27,8 @@ import {
PendingEventOrdering, PendingEventOrdering,
Room, Room,
RoomEvent, RoomEvent,
RoomMember,
RoomState,
TimelineWindow, TimelineWindow,
} from "matrix-js-sdk/src/matrix"; } from "matrix-js-sdk/src/matrix";
import { EventTimeline } from "matrix-js-sdk/src/models/event-timeline"; import { EventTimeline } from "matrix-js-sdk/src/models/event-timeline";
@ -44,7 +46,8 @@ import MatrixClientContext from "../../../src/contexts/MatrixClientContext";
import { MatrixClientPeg } from "../../../src/MatrixClientPeg"; import { MatrixClientPeg } from "../../../src/MatrixClientPeg";
import SettingsStore from "../../../src/settings/SettingsStore"; import SettingsStore from "../../../src/settings/SettingsStore";
import { isCallEvent } from "../../../src/components/structures/LegacyCallEventGrouper"; import { isCallEvent } from "../../../src/components/structures/LegacyCallEventGrouper";
import { flushPromises, mkRoom, stubClient } from "../../test-utils"; import { flushPromises, mkMembership, mkRoom, stubClient } from "../../test-utils";
import { mkThread } from "../../test-utils/threads";
const newReceipt = (eventId: string, userId: string, readTs: number, fullyReadTs: number): MatrixEvent => { const newReceipt = (eventId: string, userId: string, readTs: number, fullyReadTs: number): MatrixEvent => {
const receiptContent = { const receiptContent = {
@ -60,7 +63,7 @@ const newReceipt = (eventId: string, userId: string, readTs: number, fullyReadTs
const getProps = (room: Room, events: MatrixEvent[]): TimelinePanel["props"] => { const getProps = (room: Room, events: MatrixEvent[]): TimelinePanel["props"] => {
const timelineSet = { room: room as Room } as EventTimelineSet; const timelineSet = { room: room as Room } as EventTimelineSet;
const timeline = new EventTimeline(timelineSet); const timeline = new EventTimeline(timelineSet);
events.forEach((event) => timeline.addEvent(event, true)); events.forEach((event) => timeline.addEvent(event, { toStartOfTimeline: true }));
timelineSet.getLiveTimeline = () => timeline; timelineSet.getLiveTimeline = () => timeline;
timelineSet.getTimelineForEvent = () => timeline; timelineSet.getTimelineForEvent = () => timeline;
timelineSet.getPendingEvents = () => events; timelineSet.getPendingEvents = () => events;
@ -430,7 +433,7 @@ describe("TimelinePanel", () => {
// @ts-ignore // @ts-ignore
thread.fetchEditsWhereNeeded = () => Promise.resolve(); thread.fetchEditsWhereNeeded = () => Promise.resolve();
await thread.addEvent(reply1, true); await thread.addEvent(reply1, true);
await allThreads.getLiveTimeline().addEvent(thread.rootEvent!, true); await allThreads.getLiveTimeline().addEvent(thread.rootEvent!, { toStartOfTimeline: true });
const replyToEvent = jest.spyOn(thread, "replyToEvent", "get"); const replyToEvent = jest.spyOn(thread, "replyToEvent", "get");
const dom = render( const dom = render(
@ -476,7 +479,7 @@ describe("TimelinePanel", () => {
// @ts-ignore // @ts-ignore
realThread.fetchEditsWhereNeeded = () => Promise.resolve(); realThread.fetchEditsWhereNeeded = () => Promise.resolve();
await realThread.addEvent(reply1, true); await realThread.addEvent(reply1, true);
await allThreads.getLiveTimeline().addEvent(realThread.rootEvent!, true); await allThreads.getLiveTimeline().addEvent(realThread.rootEvent!, { toStartOfTimeline: true });
const replyToEvent = jest.spyOn(realThread, "replyToEvent", "get"); const replyToEvent = jest.spyOn(realThread, "replyToEvent", "get");
// @ts-ignore // @ts-ignore
@ -513,4 +516,66 @@ describe("TimelinePanel", () => {
replyToEvent.mockClear(); replyToEvent.mockClear();
}); });
}); });
it("renders when the last message is an undecryptable thread root", async () => {
jest.spyOn(SettingsStore, "getValue").mockImplementation((name) => name === "feature_threadstable");
const client = MatrixClientPeg.get();
client.isRoomEncrypted = () => true;
client.supportsExperimentalThreads = () => true;
client.decryptEventIfNeeded = () => Promise.resolve();
const authorId = client.getUserId()!;
const room = new Room("roomId", client, authorId, {
lazyLoadMembers: false,
pendingEventOrdering: PendingEventOrdering.Detached,
});
const events = mockEvents(room);
const timelineSet = room.getUnfilteredTimelineSet();
const { rootEvent } = mkThread({
room,
client,
authorId,
participantUserIds: [authorId],
});
events.push(rootEvent);
events.forEach((event) => timelineSet.getLiveTimeline().addEvent(event, { toStartOfTimeline: true }));
const roomMembership = mkMembership({
mship: "join",
prevMship: "join",
user: authorId,
room: room.roomId,
event: true,
skey: "123",
});
events.push(roomMembership);
const member = new RoomMember(room.roomId, authorId);
member.membership = "join";
const roomState = new RoomState(room.roomId);
jest.spyOn(roomState, "getMember").mockReturnValue(member);
jest.spyOn(timelineSet.getLiveTimeline(), "getState").mockReturnValue(roomState);
timelineSet.addEventToTimeline(roomMembership, timelineSet.getLiveTimeline(), { toStartOfTimeline: false });
for (const event of events) {
jest.spyOn(event, "isDecryptionFailure").mockReturnValue(true);
jest.spyOn(event, "shouldAttemptDecryption").mockReturnValue(false);
}
const { container } = render(
<MatrixClientContext.Provider value={client}>
<TimelinePanel timelineSet={timelineSet} manageReadReceipts={true} sendReadReceiptOnLoad={true} />
</MatrixClientContext.Provider>,
);
await waitFor(() => expect(screen.queryByRole("progressbar")).toBeNull());
await waitFor(() => expect(container.querySelector(".mx_RoomView_MessageList")).not.toBeEmptyDOMElement());
});
}); });