Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Wrap EventTile rather than its children in an error boundary #7945

Merged
merged 5 commits into from
Mar 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions src/components/structures/MessagePanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import SettingsStore from '../../settings/SettingsStore';
import RoomContext, { TimelineRenderingType } from "../../contexts/RoomContext";
import { Layout } from "../../settings/enums/Layout";
import { _t } from "../../languageHandler";
import EventTile, { haveTileForEvent, IReadReceiptProps } from "../views/rooms/EventTile";
import EventTile, { UnwrappedEventTile, haveTileForEvent, IReadReceiptProps } from "../views/rooms/EventTile";
import { hasText } from "../../TextForEvent";
import IRCTimelineProfileResizer from "../views/elements/IRCTimelineProfileResizer";
import DMRoomMap from "../../utils/DMRoomMap";
Expand Down Expand Up @@ -251,7 +251,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {
private scrollPanel = createRef<ScrollPanel>();

private readonly showTypingNotificationsWatcherRef: string;
private eventTiles: Record<string, EventTile> = {};
private eventTiles: Record<string, UnwrappedEventTile> = {};

// A map to allow groupers to maintain consistent keys even if their first event is uprooted due to back-pagination.
public grouperKeyMap = new WeakMap<MatrixEvent, string>();
Expand Down Expand Up @@ -336,7 +336,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {
return this.eventTiles[eventId]?.ref?.current;
}

public getTileForEventId(eventId: string): EventTile {
public getTileForEventId(eventId: string): UnwrappedEventTile {
if (!this.eventTiles) {
return undefined;
}
Expand Down Expand Up @@ -919,7 +919,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {
return receiptsByEvent;
}

private collectEventTile = (eventId: string, node: EventTile): void => {
private collectEventTile = (eventId: string, node: UnwrappedEventTile): void => {
this.eventTiles[eventId] = node;
};

Expand Down
20 changes: 11 additions & 9 deletions src/components/views/rooms/EventTile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

import React, { createRef, MouseEvent } from 'react';
import React, { createRef, forwardRef, MouseEvent, RefObject } from 'react';
import classNames from "classnames";
import { EventType, MsgType, RelationType } from "matrix-js-sdk/src/@types/event";
import { EventStatus, MatrixEvent, MatrixEventEvent } from "matrix-js-sdk/src/models/event";
Expand Down Expand Up @@ -356,7 +356,7 @@ interface IState {

// MUST be rendered within a RoomContext with a set timelineRenderingType
@replaceableComponent("views.rooms.EventTile")
export default class EventTile extends React.Component<IProps, IState> {
export class UnwrappedEventTile extends React.Component<IProps, IState> {
private suppressReadReceiptAnimation: boolean;
private isListeningForReceipts: boolean;
// TODO: Types
Expand Down Expand Up @@ -1129,7 +1129,7 @@ export default class EventTile extends React.Component<IProps, IState> {
return false;
}

private renderEvent() {
public render() {
const msgtype = this.props.mxEvent.getContent().msgtype;
const eventType = this.props.mxEvent.getType() as EventType;
const {
Expand Down Expand Up @@ -1652,14 +1652,16 @@ export default class EventTile extends React.Component<IProps, IState> {
}
}
}

public render() {
return <TileErrorBoundary mxEvent={this.props.mxEvent} layout={this.props.layout}>
{ this.renderEvent() }
</TileErrorBoundary>;
}
}

// Wrap all event tiles with the tile error boundary so that any throws even during construction are captured
const SafeEventTile = forwardRef((props: IProps, ref: RefObject<UnwrappedEventTile>) => {
return <TileErrorBoundary mxEvent={props.mxEvent} layout={props.layout}>
<UnwrappedEventTile ref={ref} {...props} />
</TileErrorBoundary>;
});
export default SafeEventTile;

// XXX this'll eventually be dynamic based on the fields once we have extensible event types
const messageTypes = [EventType.RoomMessage, EventType.Sticker];
function isMessageEvent(ev: MatrixEvent): boolean {
Expand Down
30 changes: 11 additions & 19 deletions test/components/structures/MessagePanel-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,22 +21,19 @@ import { EventEmitter } from "events";
import * as Matrix from 'matrix-js-sdk/src/matrix';
import FakeTimers from '@sinonjs/fake-timers';
import { mount } from "enzyme";
import * as TestUtils from "react-dom/test-utils";

import { MatrixClientPeg } from '../../../src/MatrixClientPeg';
import sdk from '../../skinned-sdk';
import SettingsStore from "../../../src/settings/SettingsStore";
import MatrixClientContext from "../../../src/contexts/MatrixClientContext";
import RoomContext from "../../../src/contexts/RoomContext";
import DMRoomMap from "../../../src/utils/DMRoomMap";
import { upsertRoomStateEvents } from '../../test-utils';

const TestUtils = require('react-dom/test-utils');
const expect = require('expect');
import { UnwrappedEventTile } from "../../../src/components/views/rooms/EventTile";
import * as TestUtilsMatrix from "../../test-utils";

const MessagePanel = sdk.getComponent('structures.MessagePanel');

const TestUtilsMatrix = require('../../test-utils');

let client;
const room = new Matrix.Room("!roomId:server_name");

Expand Down Expand Up @@ -288,8 +285,7 @@ describe('MessagePanel', function() {
);

// just check we have the right number of tiles for now
const tiles = TestUtils.scryRenderedComponentsWithType(
res, sdk.getComponent('rooms.EventTile'));
const tiles = TestUtils.scryRenderedComponentsWithType(res, UnwrappedEventTile);
expect(tiles.length).toEqual(10);
});

Expand All @@ -299,9 +295,7 @@ describe('MessagePanel', function() {
);

// just check we have the right number of tiles for now
const tiles = TestUtils.scryRenderedComponentsWithType(
res, sdk.getComponent('rooms.EventTile'),
);
const tiles = TestUtils.scryRenderedComponentsWithType(res, UnwrappedEventTile);
expect(tiles.length).toEqual(2);

const summaryTiles = TestUtils.scryRenderedComponentsWithType(
Expand All @@ -320,8 +314,7 @@ describe('MessagePanel', function() {
/>,
);

const tiles = TestUtils.scryRenderedComponentsWithType(
res, sdk.getComponent('rooms.EventTile'));
const tiles = TestUtils.scryRenderedComponentsWithType(res, UnwrappedEventTile);

// find the <li> which wraps the read marker
const rm = TestUtils.findRenderedDOMComponentWithClass(res, 'mx_RoomView_myReadMarker_container');
Expand Down Expand Up @@ -390,8 +383,7 @@ describe('MessagePanel', function() {
readMarkerVisible={true}
/>, parentDiv);

const tiles = TestUtils.scryRenderedComponentsWithType(
mp, sdk.getComponent('rooms.EventTile'));
const tiles = TestUtils.scryRenderedComponentsWithType(mp, UnwrappedEventTile);
const tileContainers = tiles.map(function(t) {
return ReactDOM.findDOMNode(t);
});
Expand Down Expand Up @@ -437,7 +429,7 @@ describe('MessagePanel', function() {

it('should collapse creation events', function() {
const events = mkCreationEvents();
upsertRoomStateEvents(room, events);
TestUtilsMatrix.upsertRoomStateEvents(room, events);
const res = mount(
<WrappedMessagePanel className="cls" events={events} />,
);
Expand All @@ -447,23 +439,23 @@ describe('MessagePanel', function() {
// should be outside of the room creation summary
// - all other events should be inside the room creation summary

const tiles = res.find(sdk.getComponent('views.rooms.EventTile'));
const tiles = res.find(UnwrappedEventTile);

expect(tiles.at(0).props().mxEvent.getType()).toEqual("m.room.create");
expect(tiles.at(1).props().mxEvent.getType()).toEqual("m.room.encryption");

const summaryTiles = res.find(sdk.getComponent('views.elements.GenericEventListSummary'));
const summaryTile = summaryTiles.at(0);

const summaryEventTiles = summaryTile.find(sdk.getComponent('views.rooms.EventTile'));
const summaryEventTiles = summaryTile.find(UnwrappedEventTile);
// every event except for the room creation, room encryption, and Bob's
// invite event should be in the event summary
expect(summaryEventTiles.length).toEqual(tiles.length - 3);
});

it('should hide read-marker at the end of creation event summary', function() {
const events = mkCreationEvents();
upsertRoomStateEvents(room, events);
TestUtilsMatrix.upsertRoomStateEvents(room, events);
const res = mount(
<WrappedMessagePanel
className="cls"
Expand Down