Rework Upload internals to use MVVM (#33307)

* Initial reword of upload to MVVM.

* Update tests

* More incremental improvements

* Refactor tests to use helper method for composer uploads.

* Add drag and drop tests

* lint

* Add commentary

* fixup test

* More precise selector

* Retarget uploads

* lint

* fixup

* one more type

* update snap

* Fixup composerUploadFiles

* fix import

* lint

* Copy and paste fixes too

* Add tests for pasting

* Add tests for pasting files.

* Remove redundant fn

* rm comment

* tidy up

* Test cleanup

* More clean up

* another fix

* Use condensed version

* Cleanup tests

* more cleaning

* last bity

* s/throw Error/throw new Error/
This commit is contained in:
Will Hunt
2026-05-12 12:30:30 +01:00
committed by GitHub
parent 39607799de
commit aeaeb55cda
26 changed files with 768 additions and 490 deletions
@@ -6,35 +6,62 @@ Please see LICENSE files in the repository root for full details.
*/
import React from "react";
import { mocked } from "jest-mock";
import { render, fireEvent } from "jest-matrix-react";
import { Room } from "matrix-js-sdk/src/matrix";
import { useMockedViewModel } from "@element-hq/web-shared-components";
import FileDropTarget from "../../../../src/components/structures/FileDropTarget.tsx";
import { stubClient } from "../../../test-utils";
import {
RoomUploadContext,
type RoomUploadViewActions,
type RoomUploadViewModel,
type RoomUploadViewSnapshot,
} from "../../../../src/viewmodels/room/RoomUploadViewModel.tsx";
function FileDropTargetWrapped({
element,
snapshot,
actions,
}: {
element: HTMLDivElement;
snapshot: RoomUploadViewSnapshot;
actions: Partial<RoomUploadViewActions>;
}) {
const mockVm = useMockedViewModel<RoomUploadViewSnapshot, RoomUploadViewActions>(
snapshot,
actions as RoomUploadViewActions,
);
return (
<RoomUploadContext.Provider value={mockVm as RoomUploadViewModel}>
<FileDropTarget parent={element} />
</RoomUploadContext.Provider>
);
}
describe("FileDropTarget", () => {
let room: Room;
beforeEach(() => {
const client = stubClient();
room = new Room("!roomId:example.com", client, client.getUserId()!);
room.currentState.maySendMessage = jest.fn().mockReturnValue(true);
});
it("should render nothing when idle", () => {
const element = document.createElement("div");
const onFileDrop = jest.fn();
const { asFragment } = render(<FileDropTarget room={room} onFileDrop={onFileDrop} parent={element} />);
const { asFragment } = render(
<FileDropTargetWrapped
element={element}
snapshot={{ mayUpload: true }}
actions={{ initiateViaDataTransfer: onFileDrop }}
/>,
);
expect(asFragment()).toMatchSnapshot();
});
it("should render drop file prompt on mouse over with file if permissions allow", () => {
const element = document.createElement("div");
const onFileDrop = jest.fn();
mocked(room.currentState.maySendMessage).mockReturnValue(true);
const { asFragment } = render(<FileDropTarget room={room} onFileDrop={onFileDrop} parent={element} />);
const { asFragment } = render(
<FileDropTargetWrapped
element={element}
snapshot={{ mayUpload: true }}
actions={{ initiateViaDataTransfer: onFileDrop }}
/>,
);
fireEvent.dragEnter(element, {
dataTransfer: {
types: ["Files"],
@@ -46,9 +73,13 @@ describe("FileDropTarget", () => {
it("should not render drop file prompt on mouse over with file if permissions do not allow", () => {
const element = document.createElement("div");
const onFileDrop = jest.fn();
mocked(room.currentState.maySendMessage).mockReturnValue(false);
const { asFragment } = render(<FileDropTarget room={room} onFileDrop={onFileDrop} parent={element} />);
const { asFragment } = render(
<FileDropTargetWrapped
element={element}
snapshot={{ mayUpload: false }}
actions={{ initiateViaDataTransfer: onFileDrop }}
/>,
);
fireEvent.dragEnter(element, {
dataTransfer: {
types: ["Files"],
@@ -270,6 +270,12 @@ exports[`RoomView for a local room in state ERROR should match the snapshot 1`]
</div>
</div>
</main>
<input
data-testid="room-upload-context-input"
multiple=""
style="display: none;"
type="file"
/>
</div>
</div>
`;
@@ -638,16 +644,17 @@ exports[`RoomView for a local room in state NEW should match the snapshot 1`] =
/>
</svg>
</div>
<input
multiple=""
style="display: none;"
type="file"
/>
</div>
</div>
</div>
</div>
</main>
<input
data-testid="room-upload-context-input"
multiple=""
style="display: none;"
type="file"
/>
</div>
</div>
`;
@@ -1010,16 +1017,17 @@ exports[`RoomView for a local room in state NEW that is encrypted should match t
/>
</svg>
</div>
<input
multiple=""
style="display: none;"
type="file"
/>
</div>
</div>
</div>
</div>
</main>
<input
data-testid="room-upload-context-input"
multiple=""
style="display: none;"
type="file"
/>
</div>
</div>
`;
@@ -1320,6 +1328,12 @@ exports[`RoomView should hide the composer when hideComposer=true 1`] = `
/>
</div>
</div>
<input
data-testid="room-upload-context-input"
multiple=""
style="display: none;"
type="file"
/>
</div>
</div>
</div>
@@ -1621,15 +1635,16 @@ exports[`RoomView should hide the header when hideHeader=true 1`] = `
/>
</svg>
</div>
<input
multiple=""
style="display: none;"
type="file"
/>
</div>
</div>
</div>
</div>
<input
data-testid="room-upload-context-input"
multiple=""
style="display: none;"
type="file"
/>
</div>
</div>
</div>
@@ -2096,15 +2111,16 @@ exports[`RoomView should hide the pinned message banner when hidePinnedMessageBa
/>
</svg>
</div>
<input
multiple=""
style="display: none;"
type="file"
/>
</div>
</div>
</div>
</div>
<input
data-testid="room-upload-context-input"
multiple=""
style="display: none;"
type="file"
/>
</div>
</div>
</div>
@@ -2571,15 +2587,16 @@ exports[`RoomView should hide the right panel when hideRightPanel=true 1`] = `
/>
</svg>
</div>
<input
multiple=""
style="display: none;"
type="file"
/>
</div>
</div>
</div>
</div>
<input
data-testid="room-upload-context-input"
multiple=""
style="display: none;"
type="file"
/>
</div>
</div>
</div>
@@ -2794,6 +2811,12 @@ exports[`RoomView should not display the timeline when the room encryption is lo
/>
</div>
</div>
<input
data-testid="room-upload-context-input"
multiple=""
style="display: none;"
type="file"
/>
</div>
</div>
</div>
@@ -3288,15 +3311,16 @@ exports[`RoomView should not display the timeline when the room encryption is lo
/>
</svg>
</div>
<input
multiple=""
style="display: none;"
type="file"
/>
</div>
</div>
</div>
</div>
<input
data-testid="room-upload-context-input"
multiple=""
style="display: none;"
type="file"
/>
</div>
</div>
</div>
@@ -3844,15 +3868,16 @@ exports[`RoomView video rooms should render joined video room view 1`] = `
/>
</svg>
</div>
<input
multiple=""
style="display: none;"
type="file"
/>
</div>
</div>
</div>
</div>
<input
data-testid="room-upload-context-input"
multiple=""
style="display: none;"
type="file"
/>
</div>
</aside>
<div>
@@ -36,6 +36,10 @@ import UIStore, { UI_EVENTS } from "../../../../../src/stores/UIStore";
import { Action } from "../../../../../src/dispatcher/actions";
import { ScopedRoomContextProvider } from "../../../../../src/contexts/ScopedRoomContext.tsx";
import type { RoomContextType } from "../../../../../src/contexts/RoomContext.ts";
import {
RoomUploadContext,
type RoomUploadViewModel,
} from "../../../../../src/viewmodels/room/RoomUploadViewModel.tsx";
const openStickerPicker = async (): Promise<void> => {
await userEvent.click(screen.getByLabelText("More options"));
@@ -469,7 +473,9 @@ function wrapAndRender(
const getRawComponent = (props = {}, context = roomContext, client = mockClient) => (
<MatrixClientContext.Provider value={client}>
<ScopedRoomContextProvider {...context}>
<MessageComposer {...defaultProps} {...props} />
<RoomUploadContext.Provider value={{} as RoomUploadViewModel}>
<MessageComposer {...defaultProps} {...props} />
</RoomUploadContext.Provider>
</ScopedRoomContextProvider>
</MatrixClientContext.Provider>
);
@@ -14,7 +14,8 @@ import { createTestClient, getRoomContext, mkStubRoom } from "../../../../test-u
import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg";
import MessageComposerButtons from "../../../../../src/components/views/rooms/MessageComposerButtons";
import { ScopedRoomContextProvider } from "../../../../../src/contexts/ScopedRoomContext.tsx";
import type { RoomContextType } from "../../../../../src/contexts/RoomContext.ts";
import { type RoomContextType } from "../../../../../src/contexts/RoomContext.ts";
import { RoomUploadContextProvider } from "../../../../../src/viewmodels/room/RoomUploadViewModel.tsx";
describe("MessageComposerButtons", () => {
// @ts-ignore - we're deliberately not implementing the whole interface here, but
@@ -54,7 +55,9 @@ describe("MessageComposerButtons", () => {
return render(
<MatrixClientContext.Provider value={mockClient}>
<ScopedRoomContextProvider {...defaultRoomContext}>{component}</ScopedRoomContextProvider>
<ScopedRoomContextProvider {...defaultRoomContext}>
<RoomUploadContextProvider>{component}</RoomUploadContextProvider>
</ScopedRoomContextProvider>
</MatrixClientContext.Provider>,
);
}
@@ -31,6 +31,7 @@ import { doMaybeLocalRoomAction } from "../../../../../src/utils/local-room";
import { addTextToComposer } from "../../../../test-utils/composer";
import { ScopedRoomContextProvider } from "../../../../../src/contexts/ScopedRoomContext.tsx";
import { SdkContextClass } from "../../../../../src/contexts/SDKContext.ts";
import { RoomUploadContextProvider } from "../../../../../src/viewmodels/room/RoomUploadViewModel.tsx";
jest.mock("../../../../../src/utils/local-room", () => ({
doMaybeLocalRoomAction: jest.fn(),
@@ -187,8 +188,10 @@ describe("<SendMessageComposer/>", () => {
};
const getRawComponent = (props = {}, roomContext = defaultRoomContext, client = mockClient) => (
<MatrixClientContext.Provider value={client}>
<ScopedRoomContextProvider {...roomContext}>
<SendMessageComposer {...defaultProps} {...props} />
<ScopedRoomContextProvider room={mockRoom} {...roomContext}>
<RoomUploadContextProvider>
<SendMessageComposer {...defaultProps} {...props} />
</RoomUploadContextProvider>
</ScopedRoomContextProvider>
</MatrixClientContext.Provider>
);
@@ -435,7 +438,11 @@ describe("<SendMessageComposer/>", () => {
const { container } = render(
<MatrixClientContext.Provider value={cli}>
<SendMessageComposer room={room} toggleStickerPickerOpen={jest.fn()} />
<ScopedRoomContextProvider {...({ room } as unknown as RoomContextType)}>
<RoomUploadContextProvider>
<SendMessageComposer room={room} toggleStickerPickerOpen={jest.fn()} />
</RoomUploadContextProvider>
</ScopedRoomContextProvider>
</MatrixClientContext.Provider>,
);
@@ -27,6 +27,7 @@ import { type ActionPayload } from "../../../../../../src/dispatcher/payloads";
import * as EmojiButton from "../../../../../../src/components/views/rooms/EmojiButton";
import { createMocks } from "./utils";
import { ScopedRoomContextProvider } from "../../../../../../src/contexts/ScopedRoomContext.tsx";
import { RoomUploadContextProvider } from "../../../../../../src/viewmodels/room/RoomUploadViewModel.tsx";
beforeAll(initOnce, 10000);
@@ -46,7 +47,9 @@ describe("EditWysiwygComposer", () => {
return render(
<MatrixClientContext.Provider value={client}>
<ScopedRoomContextProvider {...roomContext}>
<EditWysiwygComposer disabled={disabled} editorStateTransfer={_editorStateTransfer} />
<RoomUploadContextProvider>
<EditWysiwygComposer disabled={disabled} editorStateTransfer={_editorStateTransfer} />
</RoomUploadContextProvider>
</ScopedRoomContextProvider>
</MatrixClientContext.Provider>,
);
@@ -62,7 +65,9 @@ describe("EditWysiwygComposer", () => {
rerender(
<MatrixClientContext.Provider value={mockClient}>
<ScopedRoomContextProvider {...defaultRoomContext} room={undefined}>
<EditWysiwygComposer disabled={false} editorStateTransfer={editorStateTransfer} />
<RoomUploadContextProvider>
<EditWysiwygComposer disabled={false} editorStateTransfer={editorStateTransfer} />
</RoomUploadContextProvider>
</ScopedRoomContextProvider>
</MatrixClientContext.Provider>,
);
@@ -273,7 +278,9 @@ describe("EditWysiwygComposer", () => {
render(
<MatrixClientContext.Provider value={mockClient}>
<ScopedRoomContextProvider {...defaultRoomContext}>
<EditWysiwygComposer editorStateTransfer={editorStateTransfer} />
<RoomUploadContextProvider>
<EditWysiwygComposer editorStateTransfer={editorStateTransfer} />
</RoomUploadContextProvider>
<Emoji menuPosition={{ chevronFace: ChevronFace.Top }} />
</ScopedRoomContextProvider>
</MatrixClientContext.Provider>,
@@ -25,6 +25,7 @@ import { setSelection } from "../../../../../../src/components/views/rooms/wysiw
import { createMocks } from "./utils";
import { ScopedRoomContextProvider } from "../../../../../../src/contexts/ScopedRoomContext.tsx";
import { E2EStatus } from "../../../../../../src/utils/ShieldUtils.ts";
import { RoomUploadContextProvider } from "../../../../../../src/viewmodels/room/RoomUploadViewModel.tsx";
jest.mock("../../../../../../src/components/views/rooms/EmojiButton", () => ({
EmojiButton: ({ addEmoji }: { addEmoji: (emoji: string) => void }) => {
@@ -77,15 +78,17 @@ describe("SendWysiwygComposer", () => {
return render(
<MatrixClientContext.Provider value={mockClient}>
<ScopedRoomContextProvider {...defaultRoomContext}>
<SendWysiwygComposer
onChange={onChange}
onSend={onSend}
disabled={disabled}
isRichTextEnabled={isRichTextEnabled}
menuPosition={aboveLeftOf({ top: 0, bottom: 0, right: 0 })}
placeholder={placeholder}
e2eStatus={e2eStatus}
/>
<RoomUploadContextProvider>
<SendWysiwygComposer
onChange={onChange}
onSend={onSend}
disabled={disabled}
isRichTextEnabled={isRichTextEnabled}
menuPosition={aboveLeftOf({ top: 0, bottom: 0, right: 0 })}
placeholder={placeholder}
e2eStatus={e2eStatus}
/>
</RoomUploadContextProvider>
</ScopedRoomContextProvider>
</MatrixClientContext.Provider>,
);
@@ -33,6 +33,7 @@ import type AutocompleteProvider from "../../../../../../../src/autocomplete/Aut
import * as Permalinks from "../../../../../../../src/utils/permalinks/Permalinks";
import { type PermalinkParts } from "../../../../../../../src/utils/permalinks/PermalinkConstructor";
import { ScopedRoomContextProvider } from "../../../../../../../src/contexts/ScopedRoomContext.tsx";
import { RoomUploadContextProvider } from "../../../../../../../src/viewmodels/room/RoomUploadViewModel.tsx";
beforeAll(initOnce, 10000);
@@ -42,12 +43,14 @@ describe("WysiwygComposer", () => {
return render(
<MatrixClientContext.Provider value={mockClient}>
<ScopedRoomContextProvider {...defaultRoomContext}>
<WysiwygComposer
onChange={onChange}
onSend={onSend}
disabled={disabled}
initialContent={initialContent}
/>
<RoomUploadContextProvider>
<WysiwygComposer
onChange={onChange}
onSend={onSend}
disabled={disabled}
initialContent={initialContent}
/>
</RoomUploadContextProvider>
</ScopedRoomContextProvider>
</MatrixClientContext.Provider>,
);
@@ -561,19 +564,21 @@ describe("WysiwygComposer", () => {
return render(
<MatrixClientContext.Provider value={client}>
<ScopedRoomContextProvider {...roomContext}>
<ComposerContext.Provider
value={getDefaultContextValue({ editorStateTransfer: _editorStateTransfer })}
>
<WysiwygComposer
onChange={jest.fn()}
onSend={jest.fn()}
initialContent={
roomContext.room && _editorStateTransfer
? parseEditorStateTransfer(_editorStateTransfer, roomContext.room, client)
: undefined
}
/>
</ComposerContext.Provider>
<RoomUploadContextProvider>
<ComposerContext.Provider
value={getDefaultContextValue({ editorStateTransfer: _editorStateTransfer })}
>
<WysiwygComposer
onChange={jest.fn()}
onSend={jest.fn()}
initialContent={
roomContext.room && _editorStateTransfer
? parseEditorStateTransfer(_editorStateTransfer, roomContext.room, client)
: undefined
}
/>
</ComposerContext.Provider>
</RoomUploadContextProvider>
</ScopedRoomContextProvider>
</MatrixClientContext.Provider>,
);
@@ -5,29 +5,21 @@ Copyright 2023 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import { type IEventRelation, type MatrixEvent } from "matrix-js-sdk/src/matrix";
import { waitFor } from "jest-matrix-react";
import fetchMock from "@fetch-mock/jest";
import { TimelineRenderingType } from "../../../../../../../src/contexts/RoomContext";
import { mkStubRoom, stubClient } from "../../../../../../test-utils";
import ContentMessages from "../../../../../../../src/ContentMessages";
import { type IRoomState } from "../../../../../../../src/components/structures/RoomView";
import {
handleClipboardEvent,
isEventToHandleAsClipboardEvent,
} from "../../../../../../../src/components/views/rooms/wysiwyg_composer/hooks/utils";
import type { RoomUploadViewModel } from "../../../../../../../src/viewmodels/room/RoomUploadViewModel";
import type { MockedObject } from "jest-mock";
const mockClient = stubClient();
const mockRoom = mkStubRoom("mock room", "mock room", mockClient);
const mockRoomState = {
room: mockRoom,
timelineRenderingType: TimelineRenderingType.Room,
replyToEvent: {} as unknown as MatrixEvent,
} as unknown as IRoomState;
const mockUploadVM = {
initiateViaDataTransfer: jest.fn().mockResolvedValue(undefined),
initiateViaInputFiles: jest.fn().mockResolvedValue(undefined),
} as Partial<RoomUploadViewModel> as MockedObject<RoomUploadViewModel>;
const sendContentListToRoomSpy = jest.spyOn(ContentMessages.sharedInstance(), "sendContentListToRoom");
const sendContentToRoomSpy = jest.spyOn(ContentMessages.sharedInstance(), "sendContentToRoom");
const logSpy = jest.spyOn(console, "log").mockImplementation(() => {});
describe("handleClipboardEvent", () => {
@@ -45,29 +37,16 @@ describe("handleClipboardEvent", () => {
it("returns false if it is not a paste event", () => {
const originalEvent = createMockClipboardEvent({ type: "copy" });
const output = handleClipboardEvent(originalEvent, originalEvent.clipboardData, mockRoomState, mockClient);
const output = handleClipboardEvent(originalEvent, originalEvent.clipboardData, mockUploadVM);
expect(output).toBe(false);
expect(mockUploadVM.initiateViaDataTransfer).not.toHaveBeenCalled();
});
it("returns false if clipboard data is null", () => {
const originalEvent = createMockClipboardEvent({ type: "paste", clipboardData: null });
const output = handleClipboardEvent(originalEvent, originalEvent.clipboardData, mockRoomState, mockClient);
expect(output).toBe(false);
});
it("returns false if room is undefined", () => {
const originalEvent = createMockClipboardEvent({ type: "paste" });
const { room, ...roomStateWithoutRoom } = mockRoomState;
const output = handleClipboardEvent(
originalEvent,
originalEvent.clipboardData,
roomStateWithoutRoom,
mockClient,
);
const output = handleClipboardEvent(originalEvent, originalEvent.clipboardData, mockUploadVM);
expect(output).toBe(false);
expect(mockUploadVM.initiateViaDataTransfer).not.toHaveBeenCalled();
});
it("returns false if room clipboardData files and types are empty", () => {
@@ -75,8 +54,9 @@ describe("handleClipboardEvent", () => {
type: "paste",
clipboardData: { files: [], types: [] },
});
const output = handleClipboardEvent(originalEvent, originalEvent.clipboardData, mockRoomState, mockClient);
const output = handleClipboardEvent(originalEvent, originalEvent.clipboardData, mockUploadVM);
expect(output).toBe(false);
expect(mockUploadVM.initiateViaDataTransfer).not.toHaveBeenCalled();
});
it("handles event and calls sendContentListToRoom when data files are present", () => {
@@ -84,65 +64,23 @@ describe("handleClipboardEvent", () => {
type: "paste",
clipboardData: { files: ["something here"], types: [] },
});
const output = handleClipboardEvent(originalEvent, originalEvent.clipboardData, mockRoomState, mockClient);
const mockReplyToEvent = {} as unknown as MatrixEvent;
expect(sendContentListToRoomSpy).toHaveBeenCalledTimes(1);
expect(sendContentListToRoomSpy).toHaveBeenCalledWith(
originalEvent.clipboardData?.files,
mockRoom.roomId,
undefined, // this is the event relation, an optional arg
mockReplyToEvent,
mockClient,
mockRoomState.timelineRenderingType,
);
expect(output).toBe(true);
});
it("calls sendContentListToRoom with eventRelation when present", () => {
const originalEvent = createMockClipboardEvent({
type: "paste",
clipboardData: { files: ["something here"], types: [] },
});
const mockEventRelation = {} as unknown as IEventRelation;
const mockReplyToEvent = {} as unknown as MatrixEvent;
const output = handleClipboardEvent(
originalEvent,
originalEvent.clipboardData,
mockRoomState,
mockClient,
mockEventRelation,
);
expect(sendContentListToRoomSpy).toHaveBeenCalledTimes(1);
expect(sendContentListToRoomSpy).toHaveBeenCalledWith(
originalEvent.clipboardData?.files,
mockRoom.roomId,
mockEventRelation, // this is the event relation, an optional arg
mockReplyToEvent,
mockClient,
mockRoomState.timelineRenderingType,
);
const output = handleClipboardEvent(originalEvent, originalEvent.clipboardData, mockUploadVM);
expect(mockUploadVM.initiateViaDataTransfer).toHaveBeenCalledTimes(1);
expect(mockUploadVM.initiateViaDataTransfer).toHaveBeenCalledWith(originalEvent.clipboardData);
expect(output).toBe(true);
});
it("calls the error handler when sentContentListToRoom errors", async () => {
const mockErrorMessage = "something went wrong";
sendContentListToRoomSpy.mockRejectedValueOnce(new Error(mockErrorMessage));
mockUploadVM.initiateViaDataTransfer.mockRejectedValueOnce(new Error(mockErrorMessage));
const originalEvent = createMockClipboardEvent({
type: "paste",
clipboardData: { files: ["something here"], types: [] },
});
const mockEventRelation = {} as unknown as IEventRelation;
const output = handleClipboardEvent(
originalEvent,
originalEvent.clipboardData,
mockRoomState,
mockClient,
mockEventRelation,
);
const output = handleClipboardEvent(originalEvent, originalEvent.clipboardData, mockUploadVM);
expect(sendContentListToRoomSpy).toHaveBeenCalledTimes(1);
expect(mockUploadVM.initiateViaDataTransfer).toHaveBeenCalledTimes(1);
await waitFor(() => {
expect(logSpy).toHaveBeenCalledWith(mockErrorMessage);
});
@@ -158,15 +96,7 @@ describe("handleClipboardEvent", () => {
getData: jest.fn().mockReturnValue("<div>invalid html"),
},
});
const mockEventRelation = {} as unknown as IEventRelation;
const output = handleClipboardEvent(
originalEvent,
originalEvent.clipboardData,
mockRoomState,
mockClient,
mockEventRelation,
);
const output = handleClipboardEvent(originalEvent, originalEvent.clipboardData, mockUploadVM);
expect(logSpy).toHaveBeenCalledWith("Failed to handle pasted content as Safari inserted content");
expect(output).toBe(false);
});
@@ -180,10 +110,10 @@ describe("handleClipboardEvent", () => {
getData: jest.fn().mockReturnValue(`<img src="blob:" />`),
},
});
const mockEventRelation = {} as unknown as IEventRelation;
handleClipboardEvent(originalEvent, originalEvent.clipboardData, mockRoomState, mockClient, mockEventRelation);
const output = handleClipboardEvent(originalEvent, originalEvent.clipboardData, mockUploadVM);
expect(fetchMock).toHaveFetchedTimes(1, "blob:");
expect(output).toBe(true);
});
it("calls error handler when fetch fails", async () => {
@@ -197,14 +127,7 @@ describe("handleClipboardEvent", () => {
getData: jest.fn().mockReturnValue(`<img src="blob:" />`),
},
});
const mockEventRelation = {} as unknown as IEventRelation;
const output = handleClipboardEvent(
originalEvent,
originalEvent.clipboardData,
mockRoomState,
mockClient,
mockEventRelation,
);
const output = handleClipboardEvent(originalEvent, originalEvent.clipboardData, mockUploadVM);
await waitFor(() => {
expect(logSpy).toHaveBeenCalledWith(mockErrorMessage);
@@ -212,7 +135,7 @@ describe("handleClipboardEvent", () => {
expect(output).toBe(true);
});
it("calls sendContentToRoom when parsing is successful", async () => {
it("calls initiateViaInputFiles when parsing is successful", async () => {
fetchMock.get("test/file", {
blob: () => {
return Promise.resolve({ type: "image/jpeg" } as Blob);
@@ -227,23 +150,11 @@ describe("handleClipboardEvent", () => {
getData: jest.fn().mockReturnValue(`<img src="blob:" />`),
},
});
const mockEventRelation = {} as unknown as IEventRelation;
const output = handleClipboardEvent(
originalEvent,
originalEvent.clipboardData,
mockRoomState,
mockClient,
mockEventRelation,
);
const output = handleClipboardEvent(originalEvent, originalEvent.clipboardData, mockUploadVM);
await waitFor(() => {
expect(sendContentToRoomSpy).toHaveBeenCalledWith(
expect.any(File),
mockRoom.roomId,
mockEventRelation,
mockClient,
mockRoomState.replyToEvent,
);
expect(mockUploadVM.initiateViaInputFiles).toHaveBeenCalledTimes(1);
expect(mockUploadVM.initiateViaInputFiles).toHaveBeenCalledWith([expect.any(File)]);
});
expect(output).toBe(true);
});
@@ -254,8 +165,8 @@ describe("handleClipboardEvent", () => {
return Promise.resolve({ type: "image/jpeg" } as Blob);
},
});
const mockErrorMessage = "sendContentToRoom failed";
sendContentToRoomSpy.mockRejectedValueOnce(mockErrorMessage);
const mockErrorMessage = "initiateViaInputFiles failed";
mockUploadVM.initiateViaInputFiles.mockRejectedValueOnce(mockErrorMessage);
const originalEvent = createMockClipboardEvent({
type: "paste",
@@ -265,14 +176,7 @@ describe("handleClipboardEvent", () => {
getData: jest.fn().mockReturnValue(`<img src="blob:" />`),
},
});
const mockEventRelation = {} as unknown as IEventRelation;
const output = handleClipboardEvent(
originalEvent,
originalEvent.clipboardData,
mockRoomState,
mockClient,
mockEventRelation,
);
const output = handleClipboardEvent(originalEvent, originalEvent.clipboardData, mockUploadVM);
await waitFor(() => {
expect(logSpy).toHaveBeenCalledWith(mockErrorMessage);