diff --git a/src/components/viewmodels/roomlist/MessagePreviewViewModel.tsx b/src/components/viewmodels/roomlist/MessagePreviewViewModel.tsx new file mode 100644 index 0000000000..9e141c1379 --- /dev/null +++ b/src/components/viewmodels/roomlist/MessagePreviewViewModel.tsx @@ -0,0 +1,57 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * 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 { useCallback, useEffect, useState } from "react"; + +import type { Room } from "matrix-js-sdk/src/matrix"; +import { type MessagePreview, MessagePreviewStore } from "../../../stores/room-list/MessagePreviewStore"; +import { useEventEmitter } from "../../../hooks/useEventEmitter"; + +interface MessagePreviewViewState { + /** + * A string representation of the message preview if available. + */ + message?: string; +} + +/** + * View model for rendering a message preview for a given room list item. + * @param room The room for which we're rendering the message preview. + * @see {@link MessagePreviewViewState} for what this view model returns. + */ +export function useMessagePreviewViewModel(room: Room): MessagePreviewViewState { + const [messagePreview, setMessagePreview] = useState(null); + + const updatePreview = useCallback(async (): Promise => { + /** + * The second argument to getPreviewForRoom is a tag id which doesn't really make + * much sense within the context of the new room list. We can pass an empty string + * to match all tags for now but we should remember to actually change the implementation + * in the store once we remove the legacy room list. + */ + const newPreview = await MessagePreviewStore.instance.getPreviewForRoom(room, ""); + setMessagePreview(newPreview); + }, [room]); + + /** + * Update when the message preview has changed for this room. + */ + useEventEmitter(MessagePreviewStore.instance, MessagePreviewStore.getPreviewChangedEventName(room), () => { + updatePreview(); + }); + + /** + * Do an initial fetch of the message preview. + */ + useEffect(() => { + updatePreview(); + }, [updatePreview]); + + return { + message: messagePreview?.text, + }; +} diff --git a/test/unit-tests/components/viewmodels/roomlist/MessagePreviewViewModel-test.tsx b/test/unit-tests/components/viewmodels/roomlist/MessagePreviewViewModel-test.tsx new file mode 100644 index 0000000000..6c5b121022 --- /dev/null +++ b/test/unit-tests/components/viewmodels/roomlist/MessagePreviewViewModel-test.tsx @@ -0,0 +1,58 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * 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 { renderHook, waitFor } from "jest-matrix-react"; +import { type Room } from "matrix-js-sdk/src/matrix"; + +import { createTestClient, mkStubRoom } from "../../../../test-utils"; +import { type MessagePreview, MessagePreviewStore } from "../../../../../src/stores/room-list/MessagePreviewStore"; +import { useMessagePreviewViewModel } from "../../../../../src/components/viewmodels/roomlist/MessagePreviewViewModel"; + +describe("MessagePreviewViewModel", () => { + let room: Room; + + beforeEach(() => { + const matrixClient = createTestClient(); + room = mkStubRoom("roomId", "roomName", matrixClient); + }); + + it("should do an initial fetch of the message preview", async () => { + // Mock the store to return some text. + jest.spyOn(MessagePreviewStore.instance, "getPreviewForRoom").mockImplementation(async (room) => { + return { text: "Hello world!" } as MessagePreview; + }); + + const { result: vm } = renderHook(() => useMessagePreviewViewModel(room)); + + // Eventually, vm.message should have the text from the store. + await waitFor(() => { + expect(vm.current.message).toEqual("Hello world!"); + }); + }); + + it("should fetch message preview again on update from store", async () => { + // Mock the store to return the text in variable message. + let message = "Hello World!"; + jest.spyOn(MessagePreviewStore.instance, "getPreviewForRoom").mockImplementation(async (room) => { + return { text: message } as MessagePreview; + }); + jest.spyOn(MessagePreviewStore, "getPreviewChangedEventName").mockImplementation((room) => { + return "UPDATE"; + }); + + const { result: vm } = renderHook(() => useMessagePreviewViewModel(room)); + + // Let's assume the message changed. + message = "New message!"; + MessagePreviewStore.instance.emit("UPDATE"); + + /// vm.message should be the updated message. + await waitFor(() => { + expect(vm.current.message).toEqual(message); + }); + }); +});