Skip to content
Draft
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
1 change: 1 addition & 0 deletions apps/web/jest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const config: Config = {
globalSetup: "<rootDir>/test/globalSetup.ts",
setupFiles: ["jest-canvas-mock", "web-streams-polyfill/polyfill"],
setupFilesAfterEnv: ["<rootDir>/test/setupTests.ts"],
restoreMocks: true,
moduleNameMapper: {
// Support CSS module
"\\.(module.css)$": "identity-obj-proxy",
Expand Down
17 changes: 17 additions & 0 deletions apps/web/test/setupTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import "@testing-library/jest-dom";
import "blob-polyfill";
import { secureRandomString } from "matrix-js-sdk/src/randomstring";
import { mocked } from "jest-mock";
import React, { useRef } from "react";

import { PredictableRandom } from "./test-utils/predictableRandom";
import * as rageshake from "../src/rageshake/rageshake";
Expand Down Expand Up @@ -39,6 +40,22 @@ beforeEach(() => {
});
});

// Mock useId to return a predictable, incrementing string
let idCounter = 0;

// Reset the counter before each test so 'test-id-0' is always first
beforeEach(() => {
idCounter = 0;

jest.spyOn(React, "useId").mockImplementation(() => {
// IDs need to be consistent for re-renders of the same component otherwise Radix etc get confused.
// We can use a ref to store the one we generated which is broadly similar to how react's version
// works in that it generates one at mount time and returns that).
const myId = useRef(`test-id-${idCounter++}`);
return myId.current;
});
});

// Somewhat hacky workaround for https://github.com/jestjs/jest/issues/15747: if the GHA reporter is enabled,
// capture logs using the rageshake infrastructure, then dump them out after the test.
if (env["GITHUB_ACTIONS"] !== undefined) {
Expand Down
120 changes: 68 additions & 52 deletions apps/web/test/unit-tests/components/structures/MessagePanel-test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,14 @@ Please see LICENSE files in the repository root for full details.

import React from "react";
import { EventEmitter } from "events";
import { type MatrixEvent, Room, RoomMember, type Thread, ReceiptType } from "matrix-js-sdk/src/matrix";
import {
type MatrixEvent,
Room,
RoomMember,
type Thread,
ReceiptType,
type MatrixClient,
} from "matrix-js-sdk/src/matrix";
import { KnownMembership } from "matrix-js-sdk/src/types";
import { render, within } from "jest-matrix-react";

Expand Down Expand Up @@ -42,22 +49,9 @@ const roomId = "!roomId:server_name";
describe("MessagePanel", function () {
const events = mkEvents();
const userId = "@me:here";
const client = getMockClientWithEventEmitter({
...mockClientMethodsUser(userId),
...mockClientMethodsEvents(),
...mockClientMethodsCrypto(),
...mockClientPushProcessor(),
getAccountData: jest.fn(),
isUserIgnored: jest.fn().mockReturnValue(false),
isRoomEncrypted: jest.fn().mockReturnValue(false),
getRoom: jest.fn(),
getClientWellKnown: jest.fn().mockReturnValue({}),
supportsThreads: jest.fn().mockReturnValue(true),
});
let client: MatrixClient;
let sdkContext: SdkContextClass;
jest.spyOn(MatrixClientPeg, "get").mockReturnValue(client);

const room = new Room(roomId, client, userId);
let room: Room;

const bobMember = new RoomMember(roomId, "@bob:id");
bobMember.name = "Bob";
Expand All @@ -73,16 +67,13 @@ describe("MessagePanel", function () {
const defaultProps = {
resizeNotifier: new EventEmitter() as unknown as ResizeNotifier,
callEventGroupers: new Map(),
room,
className: "cls",
events: [] as MatrixEvent[],
};

const defaultRoomContext = {
...RoomContext,
timelineRenderingType: TimelineRenderingType.Room,
room,
roomId: room.roomId,
canReact: true,
canSendMessages: true,
showReadReceipts: true,
Expand All @@ -93,14 +84,30 @@ describe("MessagePanel", function () {
showHiddenEvents: false,
} as unknown as RoomContextType;

const getComponent = (props = {}, roomContext: Partial<RoomContextType> = {}) => (
<ScopedRoomContextProvider {...defaultRoomContext} {...roomContext}>
<MessagePanel {...defaultProps} {...props} />
const getComponent = (room: Room, props = {}, roomContext: Partial<RoomContextType> = {}) => (
<ScopedRoomContextProvider room={room} roomId={room.roomId} {...defaultRoomContext} {...roomContext}>
<MessagePanel room={room} {...defaultProps} {...props} />
</ScopedRoomContextProvider>
);

beforeEach(function () {
jest.clearAllMocks();
client = getMockClientWithEventEmitter({
...mockClientMethodsUser(userId),
...mockClientMethodsEvents(),
...mockClientMethodsCrypto(),
...mockClientPushProcessor(),
getAccountData: jest.fn(),
isUserIgnored: jest.fn().mockReturnValue(false),
isRoomEncrypted: jest.fn().mockReturnValue(false),
getRoom: jest.fn(),
getClientWellKnown: jest.fn().mockReturnValue({}),
supportsThreads: jest.fn().mockReturnValue(true),
});

jest.spyOn(MatrixClientPeg, "get").mockReturnValue(client);

room = new Room(roomId, client, userId);

// HACK: We assume all settings want to be disabled
jest.spyOn(SettingsStore, "getValue").mockImplementation((arg) => {
return arg === "showDisplaynameChanges";
Expand Down Expand Up @@ -315,7 +322,10 @@ describe("MessagePanel", function () {
}

it("should show the events", function () {
const { container } = render(getComponent({ events }), clientAndSDKContextRenderOptions(client, sdkContext));
const { container } = render(
getComponent(room, { events }),
clientAndSDKContextRenderOptions(client, sdkContext),
);

// just check we have the right number of tiles for now
const tiles = container.getElementsByClassName("mx_EventTile");
Expand All @@ -324,7 +334,7 @@ describe("MessagePanel", function () {

it("should collapse adjacent member events", function () {
const { container } = render(
getComponent({ events: mkMelsEvents() }),
getComponent(room, { events: mkMelsEvents() }),
clientAndSDKContextRenderOptions(client, sdkContext),
);

Expand All @@ -338,7 +348,7 @@ describe("MessagePanel", function () {

it("should insert the read-marker in the right place", function () {
const { container } = render(
getComponent({
getComponent(room, {
events,
readMarkerEventId: events[4].getId(),
readMarkerVisible: true,
Expand All @@ -359,7 +369,7 @@ describe("MessagePanel", function () {
it("should show the read-marker that fall in summarised events after the summary", function () {
const melsEvents = mkMelsEvents();
const { container } = render(
getComponent({
getComponent(room, {
events: melsEvents,
readMarkerEventId: melsEvents[4].getId(),
readMarkerVisible: true,
Expand All @@ -382,7 +392,7 @@ describe("MessagePanel", function () {
const melsEvents = mkMelsEventsOnly();

const { container } = render(
getComponent({
getComponent(room, {
events: melsEvents,
readMarkerEventId: melsEvents[9].getId(),
readMarkerVisible: true,
Expand All @@ -407,7 +417,7 @@ describe("MessagePanel", function () {

const { container, rerender } = render(
<div>
{getComponent({
{getComponent(room, {
events,
readMarkerEventId: events[4].getId(),
readMarkerVisible: true,
Expand All @@ -424,7 +434,7 @@ describe("MessagePanel", function () {

rerender(
<div>
{getComponent({
{getComponent(room, {
events,
readMarkerEventId: events[6].getId(),
readMarkerVisible: true,
Expand Down Expand Up @@ -453,10 +463,13 @@ describe("MessagePanel", function () {
const events = mkCreationEvents();
const createEvent = events.find((event) => event.getType() === "m.room.create")!;
const encryptionEvent = events.find((event) => event.getType() === "m.room.encryption")!;
client.getRoom.mockImplementation((id) => (id === createEvent!.getRoomId() ? room : null));
jest.mocked(client.getRoom).mockImplementation((id) => (id === createEvent!.getRoomId() ? room : null));
TestUtilsMatrix.upsertRoomStateEvents(room, events);

const { container } = render(getComponent({ events }), clientAndSDKContextRenderOptions(client, sdkContext));
const { container } = render(
getComponent(room, { events }),
clientAndSDKContextRenderOptions(client, sdkContext),
);

// we expect that
// - the room creation event, the room encryption event, and Alice inviting Bob,
Expand Down Expand Up @@ -485,7 +498,7 @@ describe("MessagePanel", function () {
const combinedEvents = [...events, beaconInfoEvent];
TestUtilsMatrix.upsertRoomStateEvents(room, combinedEvents);
const { container } = render(
getComponent({ events: combinedEvents }),
getComponent(room, { events: combinedEvents }),
clientAndSDKContextRenderOptions(client, sdkContext),
);

Expand All @@ -500,11 +513,11 @@ describe("MessagePanel", function () {
it("should hide read-marker at the end of creation event summary", function () {
const events = mkCreationEvents();
const createEvent = events.find((event) => event.getType() === "m.room.create");
client.getRoom.mockImplementation((id) => (id === createEvent!.getRoomId() ? room : null));
jest.mocked(client.getRoom).mockImplementation((id) => (id === createEvent!.getRoomId() ? room : null));
TestUtilsMatrix.upsertRoomStateEvents(room, events);

const { container } = render(
getComponent({
getComponent(room, {
events,
readMarkerEventId: events[5].getId(),
readMarkerVisible: true,
Expand All @@ -527,7 +540,7 @@ describe("MessagePanel", function () {
it("should render Date separators for the events", function () {
const events = mkOneDayEvents();
const { queryAllByRole } = render(
getComponent({ events }),
getComponent(room, { events }),
clientAndSDKContextRenderOptions(client, sdkContext),
);
const dates = queryAllByRole("separator");
Expand All @@ -539,7 +552,7 @@ describe("MessagePanel", function () {
const events = mkMelsEvents().slice(1, 11);

const { container, rerender } = render(
getComponent({ events }),
getComponent(room, { events }),
clientAndSDKContextRenderOptions(client, sdkContext),
);
let els = container.getElementsByClassName("mx_GenericEventListSummary");
Expand All @@ -560,7 +573,7 @@ describe("MessagePanel", function () {
name: "A user",
}),
];
rerender(getComponent({ events: updatedEvents }));
rerender(getComponent(room, { events: updatedEvents }));

els = container.getElementsByClassName("mx_GenericEventListSummary");
expect(els.length).toEqual(1);
Expand All @@ -572,7 +585,7 @@ describe("MessagePanel", function () {
const events = mkMelsEvents().slice(1, 11);

const { container, rerender } = render(
getComponent({ events }),
getComponent(room, { events }),
clientAndSDKContextRenderOptions(client, sdkContext),
);
let els = container.getElementsByClassName("mx_GenericEventListSummary");
Expand All @@ -593,7 +606,7 @@ describe("MessagePanel", function () {
}),
...events,
];
rerender(getComponent({ events: updatedEvents }));
rerender(getComponent(room, { events: updatedEvents }));

els = container.getElementsByClassName("mx_GenericEventListSummary");
expect(els.length).toEqual(1);
Expand All @@ -605,7 +618,7 @@ describe("MessagePanel", function () {
const events = mkMelsEvents().slice(1, 11);

const { container, rerender } = render(
getComponent({ events }),
getComponent(room, { events }),
clientAndSDKContextRenderOptions(client, sdkContext),
);
let els = container.getElementsByClassName("mx_GenericEventListSummary");
Expand All @@ -623,7 +636,7 @@ describe("MessagePanel", function () {
}),
...events.slice(5, 10),
];
rerender(getComponent({ events: updatedEvents }));
rerender(getComponent(room, { events: updatedEvents }));

// summaries split becuase room messages are not summarised
els = container.getElementsByClassName("mx_GenericEventListSummary");
Expand All @@ -640,12 +653,15 @@ describe("MessagePanel", function () {
it("doesn't lookup showHiddenEventsInTimeline while rendering", () => {
// We're only interested in the setting lookups that happen on every render,
// rather than those happening on first mount, so let's get those out of the way
const { rerender } = render(getComponent({ events: [] }), clientAndSDKContextRenderOptions(client, sdkContext));
const { rerender } = render(
getComponent(room, { events: [] }),
clientAndSDKContextRenderOptions(client, sdkContext),
);

// Set up our spy and re-render with new events
const settingsSpy = jest.spyOn(SettingsStore, "getValue").mockClear();

rerender(getComponent({ events: mkMixedHiddenAndShownEvents() }));
rerender(getComponent(room, { events: mkMixedHiddenAndShownEvents() }));

expect(settingsSpy).not.toHaveBeenCalledWith("showHiddenEventsInTimeline");
settingsSpy.mockRestore();
Expand Down Expand Up @@ -679,7 +695,7 @@ describe("MessagePanel", function () {
}),
];
const { container } = render(
getComponent({ events }, { showHiddenEvents: true }),
getComponent(room, { events }, { showHiddenEvents: true }),
clientAndSDKContextRenderOptions(client, sdkContext),
);

Expand All @@ -706,7 +722,7 @@ describe("MessagePanel", function () {
);
}
const { asFragment } = render(
getComponent({ events }, { showHiddenEvents: false }),
getComponent(room, { events }, { showHiddenEvents: false }),
clientAndSDKContextRenderOptions(client, sdkContext),
);
expect(asFragment()).toMatchSnapshot();
Expand All @@ -730,7 +746,7 @@ describe("MessagePanel", function () {
);
}
const { asFragment } = render(
getComponent({ events }, { showHiddenEvents: false }),
getComponent(room, { events }, { showHiddenEvents: false }),
clientAndSDKContextRenderOptions(client, sdkContext),
);
expect(asFragment()).toMatchSnapshot();
Expand All @@ -754,7 +770,7 @@ describe("MessagePanel", function () {
);
}
const { asFragment } = render(
getComponent({ events }, { showHiddenEvents: true }),
getComponent(room, { events }, { showHiddenEvents: true }),
clientAndSDKContextRenderOptions(client, sdkContext),
);
const cpt = asFragment();
Expand All @@ -769,7 +785,7 @@ describe("MessagePanel", function () {
});

it("should set lastSuccessful=true on non-last event if last event is not eligible for special receipt", () => {
client.getRoom.mockImplementation((id) => (id === room.roomId ? room : null));
jest.mocked(client.getRoom).mockImplementation((id) => (id === room.roomId ? room : null));
const events = [
TestUtilsMatrix.mkMessage({
event: true,
Expand All @@ -788,7 +804,7 @@ describe("MessagePanel", function () {
}),
];
const { container } = render(
getComponent({ events, showReadReceipts: true }),
getComponent(room, { events, showReadReceipts: true }),
clientAndSDKContextRenderOptions(client, sdkContext),
);

Expand All @@ -799,7 +815,7 @@ describe("MessagePanel", function () {
});

it("should set lastSuccessful=false on non-last event if last event has a receipt from someone else", () => {
client.getRoom.mockImplementation((id) => (id === room.roomId ? room : null));
jest.mocked(client.getRoom).mockImplementation((id) => (id === room.roomId ? room : null));
const events = [
TestUtilsMatrix.mkMessage({
event: true,
Expand All @@ -824,7 +840,7 @@ describe("MessagePanel", function () {
true,
);
const { container } = render(
getComponent({ events, showReadReceipts: true }),
getComponent(room, { events, showReadReceipts: true }),
clientAndSDKContextRenderOptions(client, sdkContext),
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ exports[`FilePanel renders empty state 1`] = `
</p>
</div>
<button
aria-labelledby="_r_0_"
aria-labelledby="test-id-0"
class="_icon-button_1215g_8"
data-kind="secondary"
data-testid="base-card-close-button"
Expand Down
Loading
Loading