Where the host can't delegate the user's click (Firefox, Safari, WebKitGTK), lotus.20 showed EC's own screenshare button floating in the frame's corner, apart from the host's call bar. Now the host keeps its bar button and sends the new toWidget io.lotus.prompt_screenshare; the fork shows a small "Share your screen?" card inside the frame, just above the bar, and its Share button starts the share with the frame's own click. Same two clicks as the host's confirm on Chromium. - The card focuses Share; Cancel and Escape close it; it closes if a share starts some other way or the room's call policy stops allowing sharing, and never opens where the policy forbids it (set_frame_screenshare). - Picture-in-picture (~158 px tall): compact card without the description. - controls_state: frameScreenshare is now always false (the corner button is gone), screensharePrompt says the host should use the prompt, and screensharePromptOpen lets the host stop covering the frame meanwhile. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
126 lines
4.1 KiB
TypeScript
126 lines
4.1 KiB
TypeScript
/*
|
|
Copyright 2026 Lotus Guild
|
|
|
|
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
|
Please see LICENSE in the repository root for full details.
|
|
*/
|
|
|
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
import { act, render, screen } from "@testing-library/react";
|
|
import userEvent from "@testing-library/user-event";
|
|
import { TooltipProvider } from "@vector-im/compound-web";
|
|
import { BehaviorSubject } from "rxjs";
|
|
|
|
import { type CallViewModel } from "../state/CallViewModel/CallViewModel";
|
|
import { LotusScreensharePrompt } from "./LotusScreensharePrompt";
|
|
import {
|
|
lotusFrameScreenshareEnabled,
|
|
lotusFrameScreenshareVisible$,
|
|
lotusScreensharePrompt$,
|
|
shouldPromptScreenshare,
|
|
} from "./lotusControls";
|
|
|
|
// lotusFlag memoizes the URL params on first read, so set them once here.
|
|
window.history.pushState(
|
|
{},
|
|
"",
|
|
"/?lotusHostControls=1&lotusFrameScreenshare=1",
|
|
);
|
|
|
|
const mockVm = (
|
|
toggle: (() => void) | null,
|
|
sharing = new BehaviorSubject(false),
|
|
): CallViewModel =>
|
|
({
|
|
sharingScreen$: sharing,
|
|
toggleScreenSharing: toggle,
|
|
}) as unknown as CallViewModel;
|
|
|
|
const renderPrompt = (vm: CallViewModel): void => {
|
|
render(
|
|
<TooltipProvider>
|
|
<LotusScreensharePrompt vm={vm} />
|
|
</TooltipProvider>,
|
|
);
|
|
};
|
|
const prompt = (): HTMLElement | null =>
|
|
screen.queryByTestId("lotus_screenshare_prompt");
|
|
|
|
afterEach(() => {
|
|
act(() => {
|
|
lotusScreensharePrompt$.next(false);
|
|
lotusFrameScreenshareVisible$.next(true);
|
|
});
|
|
});
|
|
|
|
describe("LotusScreensharePrompt", () => {
|
|
it("is enabled by lotusHostControls + lotusFrameScreenshare", () => {
|
|
expect(lotusFrameScreenshareEnabled()).toBe(true);
|
|
});
|
|
|
|
it("stays hidden until the host asks", () => {
|
|
renderPrompt(mockVm(vi.fn()));
|
|
expect(prompt()).toBeNull();
|
|
act(() => lotusScreensharePrompt$.next(true));
|
|
expect(prompt()).toBeInTheDocument();
|
|
expect(
|
|
screen.getByRole("alertdialog", { name: "Share your screen?" }),
|
|
).toBeInTheDocument();
|
|
});
|
|
|
|
it("focuses Share, and Share starts the share and closes", async () => {
|
|
const toggle = vi.fn();
|
|
renderPrompt(mockVm(toggle));
|
|
act(() => lotusScreensharePrompt$.next(true));
|
|
const share = screen.getByTestId("lotus_screenshare_prompt_share");
|
|
expect(share).toHaveFocus();
|
|
await userEvent.click(share);
|
|
expect(toggle).toHaveBeenCalledTimes(1);
|
|
expect(prompt()).toBeNull();
|
|
});
|
|
|
|
it("Cancel and Escape close without sharing", async () => {
|
|
const toggle = vi.fn();
|
|
renderPrompt(mockVm(toggle));
|
|
act(() => lotusScreensharePrompt$.next(true));
|
|
await userEvent.click(screen.getByRole("button", { name: "Cancel" }));
|
|
expect(prompt()).toBeNull();
|
|
act(() => lotusScreensharePrompt$.next(true));
|
|
await userEvent.keyboard("{Escape}");
|
|
expect(prompt()).toBeNull();
|
|
expect(toggle).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("closes when a share starts some other way", () => {
|
|
const sharing = new BehaviorSubject(false);
|
|
renderPrompt(mockVm(vi.fn(), sharing));
|
|
act(() => lotusScreensharePrompt$.next(true));
|
|
act(() => sharing.next(true));
|
|
expect(prompt()).toBeNull();
|
|
});
|
|
|
|
it("closes when the room's policy stops allowing screensharing", () => {
|
|
renderPrompt(mockVm(vi.fn()));
|
|
act(() => lotusScreensharePrompt$.next(true));
|
|
act(() => lotusFrameScreenshareVisible$.next(false));
|
|
expect(prompt()).toBeNull();
|
|
});
|
|
|
|
it("renders nothing when screensharing is unavailable", () => {
|
|
renderPrompt(mockVm(null));
|
|
act(() => lotusScreensharePrompt$.next(true));
|
|
expect(prompt()).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe("shouldPromptScreenshare", () => {
|
|
const ok = { enabled: true, allowed: true, canShare: true, sharing: false };
|
|
it("prompts only in prompt mode, when allowed, able and not sharing", () => {
|
|
expect(shouldPromptScreenshare(ok)).toBe(true);
|
|
expect(shouldPromptScreenshare({ ...ok, enabled: false })).toBe(false);
|
|
expect(shouldPromptScreenshare({ ...ok, allowed: false })).toBe(false);
|
|
expect(shouldPromptScreenshare({ ...ok, canShare: false })).toBe(false);
|
|
expect(shouldPromptScreenshare({ ...ok, sharing: true })).toBe(false);
|
|
});
|
|
});
|