/* Copyright 2024 New Vector Ltd. SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial Please see LICENSE in the repository root for full details. */ import { afterEach, expect, test, vi } from "vitest"; import { render, screen } from "@testing-library/react"; import { type MatrixClient } from "matrix-js-sdk"; import { type FC, type PropsWithChildren } from "react"; import { ClientContextProvider } from "./ClientContext"; import { Avatar } from "./Avatar"; import { mockMatrixRoomMember, mockRtcMembership } from "./utils/test"; import { widget } from "./widget"; import { WidgetApi } from "matrix-widget-api"; const TestComponent: FC< PropsWithChildren<{ client: MatrixClient; }> > = ({ client, children }) => { return ( {children} ); }; vi.mock("./widget", () => ({ widget: { api: null, // Ideally we'd only mock this in the as a widget test so the whole module is otherwise null, but just nulling `api` by default works well enough }, })); afterEach(() => { vi.unstubAllGlobals(); }); test("should just render a placeholder when the user has no avatar", () => { const client = vi.mocked({ getAccessToken: () => "my-access-token", mxcUrlToHttp: () => vi.fn(), } as unknown as MatrixClient); vi.spyOn(client, "mxcUrlToHttp"); const member = mockMatrixRoomMember( mockRtcMembership("@alice:example.org", "AAAA"), { getMxcAvatarUrl: () => undefined, }, ); const displayName = "Alice"; render( , ); const element = screen.getByRole("img", { name: "@alice:example.org" }); expect(element.tagName).toEqual("SPAN"); expect(client.mxcUrlToHttp).toBeCalledTimes(0); }); test("should attempt to fetch authenticated media from the server", async () => { const expectedAuthUrl = "http://example.org/media/alice-avatar"; const expectedObjectURL = "my-object-url"; const accessToken = "my-access-token"; const theBlob = new Blob([]); // vitest doesn't have a implementation of create/revokeObjectURL, so we need // to delete the property. It's a bit odd, but it works. Reflect.deleteProperty(global.window.URL, "createObjectURL"); globalThis.URL.createObjectURL = vi.fn().mockReturnValue(expectedObjectURL); Reflect.deleteProperty(global.window.URL, "revokeObjectURL"); globalThis.URL.revokeObjectURL = vi.fn(); const fetchFn = vi.fn().mockResolvedValue({ blob: async () => Promise.resolve(theBlob), }); vi.stubGlobal("fetch", fetchFn); const client = vi.mocked({ getAccessToken: () => accessToken, mxcUrlToHttp: () => vi.fn(), } as unknown as MatrixClient); vi.spyOn(client, "mxcUrlToHttp").mockReturnValue(expectedAuthUrl); const member = mockMatrixRoomMember( mockRtcMembership("@alice:example.org", "AAAA"), { getMxcAvatarUrl: () => "mxc://example.org/alice-avatar", }, ); const displayName = "Alice"; render( , ); // Fetch is asynchronous, so wait for this to resolve. await vi.waitUntil(() => document.querySelector(`img[src='${expectedObjectURL}']`), ); expect(client.mxcUrlToHttp).toBeCalledTimes(1); expect(globalThis.fetch).toBeCalledWith(expectedAuthUrl, { headers: { Authorization: `Bearer ${accessToken}` }, }); }); test("should attempt to use widget API if running as a widget", async () => { const expectedMXCUrl = "mxc://example.org/alice-avatar"; const expectedObjectURL = "my-object-url"; const theBlob = new Blob([]); // vitest doesn't have a implementation of create/revokeObjectURL, so we need // to delete the property. It's a bit odd, but it works. Reflect.deleteProperty(global.window.URL, "createObjectURL"); globalThis.URL.createObjectURL = vi.fn().mockReturnValue(expectedObjectURL); Reflect.deleteProperty(global.window.URL, "revokeObjectURL"); globalThis.URL.revokeObjectURL = vi.fn(); const client = vi.mocked({ getAccessToken: () => undefined, } as unknown as MatrixClient); widget!.api = { downloadFile: vi.fn() } as unknown as WidgetApi; vi.spyOn(widget!.api, "downloadFile").mockResolvedValue({ file: theBlob }); const member = mockMatrixRoomMember( mockRtcMembership("@alice:example.org", "AAAA"), { getMxcAvatarUrl: () => expectedMXCUrl, }, ); const displayName = "Alice"; render( , ); // Fetch is asynchronous, so wait for this to resolve. await vi.waitUntil(() => document.querySelector(`img[src='${expectedObjectURL}']`), ); expect(widget!.api.downloadFile).toBeCalledWith(expectedMXCUrl); });