/* 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 { test, expect, vi } from "vitest"; import { act, isInaccessible, render, screen } from "@testing-library/react"; import { axe } from "vitest-axe"; import userEvent from "@testing-library/user-event"; import { TooltipProvider } from "@vector-im/compound-web"; import { BehaviorSubject } from "rxjs"; import { SpotlightTile } from "./SpotlightTile"; import { mockLocalParticipant, mockMediaDevices, mockRtcMembership, mockLocalMedia, mockRemoteMedia, mockRemoteParticipant, mockRemoteScreenShare, } from "../utils/test"; import { SpotlightTileViewModel } from "../state/TileViewModel"; import { constant } from "../state/Behavior"; import { createRingingMedia, type RingingMediaViewModel, } from "../state/media/RingingMediaViewModel"; import { type MuteStates } from "../state/MuteStates"; global.IntersectionObserver = class MockIntersectionObserver { public observe(): void {} public unobserve(): void {} } as unknown as typeof IntersectionObserver; test("SpotlightTile is accessible", async () => { const vm1 = mockRemoteMedia( mockRtcMembership("@alice:example.org", "AAAA"), { rawDisplayName: "Alice", getMxcAvatarUrl: () => "mxc://adfsg", }, mockRemoteParticipant({}), ); const vm2 = mockLocalMedia( mockRtcMembership("@bob:example.org", "BBBB"), { rawDisplayName: "Bob", getMxcAvatarUrl: () => "mxc://dlskf", }, mockLocalParticipant({}), mockMediaDevices({}), ); const user = userEvent.setup(); const toggleExpanded = vi.fn(); const { container } = render( , ); expect(await axe(container)).toHaveNoViolations(); // Alice should be in the spotlight, with her name and avatar on the // first page screen.getByText("Alice"); const aliceAvatar = screen.getByRole("img"); expect(screen.queryByRole("button", { name: "common.back" })).toBe(null); // Bob should be out of the spotlight, and therefore invisible expect(isInaccessible(screen.getByText("Bob"))).toBe(true); // Now navigate to Bob await user.click(screen.getByRole("button", { name: "Next" })); screen.getByText("Bob"); expect(screen.getByRole("img")).not.toBe(aliceAvatar); expect(isInaccessible(screen.getByText("Alice"))).toBe(true); // Can toggle whether the tile is expanded await user.click(screen.getByRole("button", { name: "Expand" })); expect(toggleExpanded).toHaveBeenCalled(); }); test("Screen share volume UI is shown when screen share has audio", async () => { const vm = mockRemoteScreenShare( mockRtcMembership("@alice:example.org", "AAAA"), {}, mockRemoteParticipant({}), ); vi.spyOn(vm, "audioEnabled$", "get").mockReturnValue(constant(true)); const toggleExpanded = vi.fn(); const { container } = render( , ); expect(await axe(container)).toHaveNoViolations(); // Volume menu button should exist expect(screen.queryByRole("button", { name: /volume/i })).toBeInTheDocument(); }); test("Screen share volume UI is hidden when screen share has no audio", async () => { const vm = mockRemoteScreenShare( mockRtcMembership("@alice:example.org", "AAAA"), {}, mockRemoteParticipant({}), ); vi.spyOn(vm, "audioEnabled$", "get").mockReturnValue(constant(false)); const toggleExpanded = vi.fn(); const { container } = render( , ); expect(await axe(container)).toHaveNoViolations(); // Volume menu button should not exist expect( screen.queryByRole("button", { name: /volume/i }), ).not.toBeInTheDocument(); }); test("SpotlightTile displays ringing media", async () => { const pickupState$ = new BehaviorSubject< RingingMediaViewModel["pickupState$"]["value"] >("ringing"); const vm = createRingingMedia({ pickupState$, muteStates: { video: { enabled$: constant(false) }, } as unknown as MuteStates, id: "test", userId: "@alice:example.org", displayName$: constant("Alice"), mxcAvatarUrl$: constant(undefined), }); const toggleExpanded = vi.fn(); const { container } = render( , ); expect(await axe(container)).toHaveNoViolations(); // Alice should be in the spotlight with the right status screen.getByText("Alice"); screen.getByText("Calling…"); // Now we time out ringing to Alice act(() => pickupState$.next("timeout")); screen.getByText("Call ended"); });