203 lines
		
	
	
		
			8.5 KiB
		
	
	
	
		
			TypeScript
		
	
	
			
		
		
	
	
			203 lines
		
	
	
		
			8.5 KiB
		
	
	
	
		
			TypeScript
		
	
	
/*
 | 
						|
Copyright 2023 The Matrix.org Foundation C.I.C.
 | 
						|
 | 
						|
Licensed under the Apache License, Version 2.0 (the "License");
 | 
						|
you may not use this file except in compliance with the License.
 | 
						|
You may obtain a copy of the License at
 | 
						|
 | 
						|
    http://www.apache.org/licenses/LICENSE-2.0
 | 
						|
 | 
						|
Unless required by applicable law or agreed to in writing, software
 | 
						|
distributed under the License is distributed on an "AS IS" BASIS,
 | 
						|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
						|
See the License for the specific language governing permissions and
 | 
						|
limitations under the License.
 | 
						|
*/
 | 
						|
 | 
						|
import React from "react";
 | 
						|
import { render } from "@testing-library/react";
 | 
						|
import { EventTimeline, MatrixEvent, Room, M_TEXT } from "matrix-js-sdk/src/matrix";
 | 
						|
import { logger } from "matrix-js-sdk/src/logger";
 | 
						|
 | 
						|
import { IBodyProps } from "../../../../src/components/views/messages/IBodyProps";
 | 
						|
import { MPollEndBody } from "../../../../src/components/views/messages/MPollEndBody";
 | 
						|
import MatrixClientContext from "../../../../src/contexts/MatrixClientContext";
 | 
						|
import { RoomPermalinkCreator } from "../../../../src/utils/permalinks/Permalinks";
 | 
						|
import { MediaEventHelper } from "../../../../src/utils/MediaEventHelper";
 | 
						|
import {
 | 
						|
    flushPromises,
 | 
						|
    getMockClientWithEventEmitter,
 | 
						|
    makePollEndEvent,
 | 
						|
    makePollStartEvent,
 | 
						|
    mockClientMethodsEvents,
 | 
						|
    mockClientMethodsUser,
 | 
						|
    setupRoomWithPollEvents,
 | 
						|
} from "../../../test-utils";
 | 
						|
 | 
						|
describe("<MPollEndBody />", () => {
 | 
						|
    const userId = "@alice:domain.org";
 | 
						|
    const roomId = "!room:domain.org";
 | 
						|
    const mockClient = getMockClientWithEventEmitter({
 | 
						|
        ...mockClientMethodsUser(userId),
 | 
						|
        ...mockClientMethodsEvents(),
 | 
						|
        getRoom: jest.fn(),
 | 
						|
        relations: jest.fn(),
 | 
						|
        fetchRoomEvent: jest.fn(),
 | 
						|
    });
 | 
						|
    const pollStartEvent = makePollStartEvent("Question?", userId, undefined, { roomId });
 | 
						|
    const pollEndEvent = makePollEndEvent(pollStartEvent.getId()!, roomId, userId, 123);
 | 
						|
 | 
						|
    const setupRoomWithEventsTimeline = async (pollEnd: MatrixEvent, pollStart?: MatrixEvent): Promise<Room> => {
 | 
						|
        if (pollStart) {
 | 
						|
            await setupRoomWithPollEvents([pollStart], [], [pollEnd], mockClient);
 | 
						|
        }
 | 
						|
        const room = mockClient.getRoom(roomId) || new Room(roomId, mockClient, userId);
 | 
						|
 | 
						|
        // end events validate against this
 | 
						|
        jest.spyOn(room.currentState, "maySendRedactionForEvent").mockImplementation(
 | 
						|
            (_evt: MatrixEvent, id: string) => {
 | 
						|
                return id === mockClient.getSafeUserId();
 | 
						|
            },
 | 
						|
        );
 | 
						|
 | 
						|
        const timelineSet = room.getUnfilteredTimelineSet();
 | 
						|
        const getTimelineForEventSpy = jest.spyOn(timelineSet, "getTimelineForEvent");
 | 
						|
        // if we have a pollStart, mock the room timeline to include it
 | 
						|
        if (pollStart) {
 | 
						|
            const eventTimeline = {
 | 
						|
                getEvents: jest.fn().mockReturnValue([pollEnd, pollStart]),
 | 
						|
            } as unknown as EventTimeline;
 | 
						|
            getTimelineForEventSpy.mockReturnValue(eventTimeline);
 | 
						|
        }
 | 
						|
        mockClient.getRoom.mockReturnValue(room);
 | 
						|
 | 
						|
        return room;
 | 
						|
    };
 | 
						|
 | 
						|
    const defaultProps = {
 | 
						|
        mxEvent: pollEndEvent,
 | 
						|
        highlightLink: "unused",
 | 
						|
        mediaEventHelper: {} as unknown as MediaEventHelper,
 | 
						|
        onHeightChanged: () => {},
 | 
						|
        onMessageAllowed: () => {},
 | 
						|
        permalinkCreator: {} as unknown as RoomPermalinkCreator,
 | 
						|
        ref: undefined as any,
 | 
						|
    };
 | 
						|
 | 
						|
    const getComponent = (props: Partial<IBodyProps> = {}) =>
 | 
						|
        render(<MPollEndBody {...defaultProps} {...props} />, {
 | 
						|
            wrapper: ({ children }) => (
 | 
						|
                <MatrixClientContext.Provider value={mockClient}>{children}</MatrixClientContext.Provider>
 | 
						|
            ),
 | 
						|
        });
 | 
						|
 | 
						|
    beforeEach(() => {
 | 
						|
        mockClient.getRoom.mockReset();
 | 
						|
        mockClient.relations.mockResolvedValue({
 | 
						|
            events: [],
 | 
						|
        });
 | 
						|
        mockClient.fetchRoomEvent.mockResolvedValue(pollStartEvent.getEffectiveEvent());
 | 
						|
    });
 | 
						|
 | 
						|
    afterEach(() => {
 | 
						|
        jest.spyOn(logger, "error").mockRestore();
 | 
						|
    });
 | 
						|
 | 
						|
    describe("when poll start event exists in current timeline", () => {
 | 
						|
        it("renders an ended poll", async () => {
 | 
						|
            await setupRoomWithEventsTimeline(pollEndEvent, pollStartEvent);
 | 
						|
            const { container } = getComponent();
 | 
						|
 | 
						|
            // ended poll rendered
 | 
						|
            expect(container).toMatchSnapshot();
 | 
						|
 | 
						|
            // didnt try to fetch start event while it was already in timeline
 | 
						|
            expect(mockClient.fetchRoomEvent).not.toHaveBeenCalled();
 | 
						|
        });
 | 
						|
 | 
						|
        it("does not render a poll tile when end event is invalid", async () => {
 | 
						|
            // sender of end event does not match start event
 | 
						|
            const invalidEndEvent = makePollEndEvent(pollStartEvent.getId()!, roomId, "@mallory:domain.org", 123);
 | 
						|
            await setupRoomWithEventsTimeline(invalidEndEvent, pollStartEvent);
 | 
						|
            const { getByText } = getComponent({ mxEvent: invalidEndEvent });
 | 
						|
 | 
						|
            // no poll tile rendered
 | 
						|
            expect(getByText("The poll has ended. Something.")).toBeTruthy();
 | 
						|
        });
 | 
						|
    });
 | 
						|
 | 
						|
    describe("when poll start event does not exist in current timeline", () => {
 | 
						|
        it("fetches the related poll start event and displays a poll tile", async () => {
 | 
						|
            await setupRoomWithEventsTimeline(pollEndEvent);
 | 
						|
            const { container, getByTestId } = getComponent();
 | 
						|
 | 
						|
            // while fetching event, only icon is shown
 | 
						|
            expect(container).toMatchSnapshot();
 | 
						|
 | 
						|
            // flush the fetch event promise
 | 
						|
            await flushPromises();
 | 
						|
 | 
						|
            expect(mockClient.fetchRoomEvent).toHaveBeenCalledWith(roomId, pollStartEvent.getId());
 | 
						|
 | 
						|
            // quick check for poll tile
 | 
						|
            expect(getByTestId("pollQuestion").innerHTML).toEqual("Question?");
 | 
						|
            expect(getByTestId("totalVotes").innerHTML).toEqual("Final result based on 0 votes");
 | 
						|
        });
 | 
						|
 | 
						|
        it("does not render a poll tile when end event is invalid", async () => {
 | 
						|
            // sender of end event does not match start event
 | 
						|
            const invalidEndEvent = makePollEndEvent(pollStartEvent.getId()!, roomId, "@mallory:domain.org", 123);
 | 
						|
            await setupRoomWithEventsTimeline(invalidEndEvent);
 | 
						|
            const { getByText } = getComponent({ mxEvent: invalidEndEvent });
 | 
						|
 | 
						|
            // flush the fetch event promise
 | 
						|
            await flushPromises();
 | 
						|
 | 
						|
            // no poll tile rendered
 | 
						|
            expect(getByText("The poll has ended. Something.")).toBeTruthy();
 | 
						|
        });
 | 
						|
 | 
						|
        it("logs an error and displays the text fallback when fetching the start event fails", async () => {
 | 
						|
            await setupRoomWithEventsTimeline(pollEndEvent);
 | 
						|
            mockClient.fetchRoomEvent.mockRejectedValue({ code: 404 });
 | 
						|
            const logSpy = jest.spyOn(logger, "error").mockImplementation(() => {});
 | 
						|
            const { getByText } = getComponent();
 | 
						|
 | 
						|
            // flush the fetch event promise
 | 
						|
            await flushPromises();
 | 
						|
 | 
						|
            // poll end event fallback text used
 | 
						|
            expect(getByText("The poll has ended. Something.")).toBeTruthy();
 | 
						|
            expect(logSpy).toHaveBeenCalledWith("Failed to fetch related poll start event", { code: 404 });
 | 
						|
        });
 | 
						|
 | 
						|
        it("logs an error and displays the extensible event text when fetching the start event fails", async () => {
 | 
						|
            await setupRoomWithEventsTimeline(pollEndEvent);
 | 
						|
            mockClient.fetchRoomEvent.mockRejectedValue({ code: 404 });
 | 
						|
            const logSpy = jest.spyOn(logger, "error").mockImplementation(() => {});
 | 
						|
            const { getByText } = getComponent();
 | 
						|
 | 
						|
            // flush the fetch event promise
 | 
						|
            await flushPromises();
 | 
						|
 | 
						|
            // poll end event fallback text used
 | 
						|
            expect(getByText("The poll has ended. Something.")).toBeTruthy();
 | 
						|
            expect(logSpy).toHaveBeenCalledWith("Failed to fetch related poll start event", { code: 404 });
 | 
						|
        });
 | 
						|
 | 
						|
        it("displays fallback text when the poll end event does not have text", async () => {
 | 
						|
            const endWithoutText = makePollEndEvent(pollStartEvent.getId()!, roomId, userId, 123);
 | 
						|
            delete endWithoutText.getContent()[M_TEXT.name];
 | 
						|
            await setupRoomWithEventsTimeline(endWithoutText);
 | 
						|
            mockClient.fetchRoomEvent.mockRejectedValue({ code: 404 });
 | 
						|
            const { getByText } = getComponent({ mxEvent: endWithoutText });
 | 
						|
 | 
						|
            // flush the fetch event promise
 | 
						|
            await flushPromises();
 | 
						|
 | 
						|
            // default fallback text used
 | 
						|
            expect(getByText("@alice:domain.org has ended a poll")).toBeTruthy();
 | 
						|
        });
 | 
						|
    });
 | 
						|
});
 |