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

Commit 782ce01

Browse files
authored
Wrap EventTile rather than its children in an error boundary (#7945)
1 parent a75100e commit 782ce01

File tree

3 files changed

+26
-32
lines changed

3 files changed

+26
-32
lines changed

src/components/structures/MessagePanel.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ import SettingsStore from '../../settings/SettingsStore';
3131
import RoomContext, { TimelineRenderingType } from "../../contexts/RoomContext";
3232
import { Layout } from "../../settings/enums/Layout";
3333
import { _t } from "../../languageHandler";
34-
import EventTile, { haveTileForEvent, IReadReceiptProps } from "../views/rooms/EventTile";
34+
import EventTile, { UnwrappedEventTile, haveTileForEvent, IReadReceiptProps } from "../views/rooms/EventTile";
3535
import { hasText } from "../../TextForEvent";
3636
import IRCTimelineProfileResizer from "../views/elements/IRCTimelineProfileResizer";
3737
import DMRoomMap from "../../utils/DMRoomMap";
@@ -251,7 +251,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {
251251
private scrollPanel = createRef<ScrollPanel>();
252252

253253
private readonly showTypingNotificationsWatcherRef: string;
254-
private eventTiles: Record<string, EventTile> = {};
254+
private eventTiles: Record<string, UnwrappedEventTile> = {};
255255

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

339-
public getTileForEventId(eventId: string): EventTile {
339+
public getTileForEventId(eventId: string): UnwrappedEventTile {
340340
if (!this.eventTiles) {
341341
return undefined;
342342
}
@@ -919,7 +919,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {
919919
return receiptsByEvent;
920920
}
921921

922-
private collectEventTile = (eventId: string, node: EventTile): void => {
922+
private collectEventTile = (eventId: string, node: UnwrappedEventTile): void => {
923923
this.eventTiles[eventId] = node;
924924
};
925925

src/components/views/rooms/EventTile.tsx

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
1515
limitations under the License.
1616
*/
1717

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

357357
// MUST be rendered within a RoomContext with a set timelineRenderingType
358358
@replaceableComponent("views.rooms.EventTile")
359-
export default class EventTile extends React.Component<IProps, IState> {
359+
export class UnwrappedEventTile extends React.Component<IProps, IState> {
360360
private suppressReadReceiptAnimation: boolean;
361361
private isListeningForReceipts: boolean;
362362
// TODO: Types
@@ -1129,7 +1129,7 @@ export default class EventTile extends React.Component<IProps, IState> {
11291129
return false;
11301130
}
11311131

1132-
private renderEvent() {
1132+
public render() {
11331133
const msgtype = this.props.mxEvent.getContent().msgtype;
11341134
const eventType = this.props.mxEvent.getType() as EventType;
11351135
const {
@@ -1652,14 +1652,16 @@ export default class EventTile extends React.Component<IProps, IState> {
16521652
}
16531653
}
16541654
}
1655-
1656-
public render() {
1657-
return <TileErrorBoundary mxEvent={this.props.mxEvent} layout={this.props.layout}>
1658-
{ this.renderEvent() }
1659-
</TileErrorBoundary>;
1660-
}
16611655
}
16621656

1657+
// Wrap all event tiles with the tile error boundary so that any throws even during construction are captured
1658+
const SafeEventTile = forwardRef((props: IProps, ref: RefObject<UnwrappedEventTile>) => {
1659+
return <TileErrorBoundary mxEvent={props.mxEvent} layout={props.layout}>
1660+
<UnwrappedEventTile ref={ref} {...props} />
1661+
</TileErrorBoundary>;
1662+
});
1663+
export default SafeEventTile;
1664+
16631665
// XXX this'll eventually be dynamic based on the fields once we have extensible event types
16641666
const messageTypes = [EventType.RoomMessage, EventType.Sticker];
16651667
function isMessageEvent(ev: MatrixEvent): boolean {

test/components/structures/MessagePanel-test.js

Lines changed: 11 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -21,22 +21,19 @@ import { EventEmitter } from "events";
2121
import * as Matrix from 'matrix-js-sdk/src/matrix';
2222
import FakeTimers from '@sinonjs/fake-timers';
2323
import { mount } from "enzyme";
24+
import * as TestUtils from "react-dom/test-utils";
2425

2526
import { MatrixClientPeg } from '../../../src/MatrixClientPeg';
2627
import sdk from '../../skinned-sdk';
2728
import SettingsStore from "../../../src/settings/SettingsStore";
2829
import MatrixClientContext from "../../../src/contexts/MatrixClientContext";
2930
import RoomContext from "../../../src/contexts/RoomContext";
3031
import DMRoomMap from "../../../src/utils/DMRoomMap";
31-
import { upsertRoomStateEvents } from '../../test-utils';
32-
33-
const TestUtils = require('react-dom/test-utils');
34-
const expect = require('expect');
32+
import { UnwrappedEventTile } from "../../../src/components/views/rooms/EventTile";
33+
import * as TestUtilsMatrix from "../../test-utils";
3534

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

38-
const TestUtilsMatrix = require('../../test-utils');
39-
4037
let client;
4138
const room = new Matrix.Room("!roomId:server_name");
4239

@@ -288,8 +285,7 @@ describe('MessagePanel', function() {
288285
);
289286

290287
// just check we have the right number of tiles for now
291-
const tiles = TestUtils.scryRenderedComponentsWithType(
292-
res, sdk.getComponent('rooms.EventTile'));
288+
const tiles = TestUtils.scryRenderedComponentsWithType(res, UnwrappedEventTile);
293289
expect(tiles.length).toEqual(10);
294290
});
295291

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

301297
// just check we have the right number of tiles for now
302-
const tiles = TestUtils.scryRenderedComponentsWithType(
303-
res, sdk.getComponent('rooms.EventTile'),
304-
);
298+
const tiles = TestUtils.scryRenderedComponentsWithType(res, UnwrappedEventTile);
305299
expect(tiles.length).toEqual(2);
306300

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

323-
const tiles = TestUtils.scryRenderedComponentsWithType(
324-
res, sdk.getComponent('rooms.EventTile'));
317+
const tiles = TestUtils.scryRenderedComponentsWithType(res, UnwrappedEventTile);
325318

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

393-
const tiles = TestUtils.scryRenderedComponentsWithType(
394-
mp, sdk.getComponent('rooms.EventTile'));
386+
const tiles = TestUtils.scryRenderedComponentsWithType(mp, UnwrappedEventTile);
395387
const tileContainers = tiles.map(function(t) {
396388
return ReactDOM.findDOMNode(t);
397389
});
@@ -437,7 +429,7 @@ describe('MessagePanel', function() {
437429

438430
it('should collapse creation events', function() {
439431
const events = mkCreationEvents();
440-
upsertRoomStateEvents(room, events);
432+
TestUtilsMatrix.upsertRoomStateEvents(room, events);
441433
const res = mount(
442434
<WrappedMessagePanel className="cls" events={events} />,
443435
);
@@ -447,23 +439,23 @@ describe('MessagePanel', function() {
447439
// should be outside of the room creation summary
448440
// - all other events should be inside the room creation summary
449441

450-
const tiles = res.find(sdk.getComponent('views.rooms.EventTile'));
442+
const tiles = res.find(UnwrappedEventTile);
451443

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

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

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

464456
it('should hide read-marker at the end of creation event summary', function() {
465457
const events = mkCreationEvents();
466-
upsertRoomStateEvents(room, events);
458+
TestUtilsMatrix.upsertRoomStateEvents(room, events);
467459
const res = mount(
468460
<WrappedMessagePanel
469461
className="cls"

0 commit comments

Comments
 (0)