Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,12 @@ import { defaultAudioDevice } from '../../../utils/mockData/device';
describe('ScreenSharePublisher component', () => {
it('renders nothing if box is undefined', () => {
const { container } = render(
<ScreenSharePublisher box={undefined} element={undefined} publisher={null} />
<ScreenSharePublisher
box={undefined}
element={undefined}
publisher={null}
isEntireScreen={false}
/>
);
expect(container).toBeEmptyDOMElement();
});
Expand All @@ -31,12 +36,29 @@ describe('ScreenSharePublisher component', () => {
const element = document.createElement('div');
element.className = 'original-class';

render(<ScreenSharePublisher box={box} element={element} publisher={mockPublisher} />);
render(
<ScreenSharePublisher
box={box}
element={element}
publisher={mockPublisher}
isEntireScreen={false}
/>
);

expect(screen.getByText('Test Stream')).toBeInTheDocument();
expect(element.style.width).toBe('100%');
expect(element.style.position).toBe('absolute');
expect(element.style.borderRadius).toBe('12px');
expect(element.style.objectFit).toBe('contain');
});

it('renders hidden message when entire screen is shared', () => {
const box = { height: 100, width: 100, top: 0, left: 0 };

render(
<ScreenSharePublisher box={box} element={undefined} publisher={null} isEntireScreen={true} />
);

expect(screen.getByText('You are sharing your screen.')).toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ import { Publisher } from '@vonage/client-sdk-video';
import VideoTile from '../VideoTile';
import ScreenShareNameDisplay from '../../ScreenShareNameDisplay';
import useTheme from '@ui/theme';
import { useTranslation } from 'react-i18next';

export type ScreenSharePublisherProps = {
box: Box | undefined;
element: HTMLElement | HTMLObjectElement | undefined;
publisher: Publisher | null;
isEntireScreen: boolean;
};

/**
Expand All @@ -24,9 +26,11 @@ const ScreenSharePublisher = ({
box,
element,
publisher,
isEntireScreen,
}: ScreenSharePublisherProps): ReactElement | undefined => {
const theme = useTheme();
const containerRef = useRef<HTMLDivElement>(null);
const { t } = useTranslation();
useEffect(() => {
if (element && containerRef.current) {
Object.assign(element.style, {
Expand All @@ -49,7 +53,13 @@ const ScreenSharePublisher = ({
ref={containerRef}
isScreenshare
>
<ScreenShareNameDisplay name={streamName} box={box} />
{isEntireScreen ? (
<div className="absolute inset-0 flex items-center justify-center text-vera-heading-4 font-vera-plain bg-vera-dark-background text-vera-on-background pointer-events-none">
{t('screenSharing.dialog.hiddenMessage')}
</div>
) : (
<ScreenShareNameDisplay name={streamName} box={box} />
)}
</VideoTile>
)
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { render } from '@testing-library/react';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import VideoTileCanvas from './VideoTileCanvas';

const mockScreenSharePublisher = vi.fn((_props: unknown) => <div>Mock ScreenSharePublisher</div>);

vi.mock('../ScreenSharePublisher/ScreenSharePublisher', () => ({
default: (props: unknown) => mockScreenSharePublisher(props),
}));

beforeEach(() => {
mockScreenSharePublisher.mockClear();
});

describe('VideoTileCanvas', () => {
it('renders without crashing', () => {
const { container } = render(
<VideoTileCanvas
isSharingScreen={false}
isEntireScreen={false}
screensharingPublisher={null}
screenshareVideoElement={undefined}
isRightPanelOpen={false}
/>
);

expect(container).toBeDefined();
});

it('passes isEntireScreen=true to ScreenSharePublisher', () => {
render(
<VideoTileCanvas
isSharingScreen={true}
isEntireScreen={true}
screensharingPublisher={{} as unknown as never}
screenshareVideoElement={undefined}
isRightPanelOpen={false}
/>
);

expect(mockScreenSharePublisher).toHaveBeenCalled();

const props = mockScreenSharePublisher.mock.calls[0][0] as {
isEntireScreen: boolean;
};

expect(props.isEntireScreen).toBe(true);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import Box from '@mui/material/Box';

export type VideoTileCanvasProps = {
isSharingScreen: boolean;
isEntireScreen: boolean;
screensharingPublisher: OTPublisher | null;
screenshareVideoElement: HTMLVideoElement | HTMLObjectElement | undefined;
isRightPanelOpen: boolean;
Expand All @@ -33,6 +34,7 @@ export type VideoTileCanvasProps = {
*/
const VideoTileCanvas = ({
isSharingScreen,
isEntireScreen,
screensharingPublisher,
screenshareVideoElement,
isRightPanelOpen,
Expand Down Expand Up @@ -120,6 +122,7 @@ const VideoTileCanvas = ({
publisher={screensharingPublisher}
box={layoutBoxes.localScreenshareBox}
element={screenshareVideoElement}
isEntireScreen={isEntireScreen}
/>
)}
{// Note: we still render hidden subscribers with flag `hidden`
Expand Down
165 changes: 164 additions & 1 deletion frontend/src/hooks/tests/useScreenShare.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,21 @@ vi.mock('@vonage/client-sdk-video', () => ({
describe('useScreenSharing', () => {
let mockVonageVideoClient: Partial<VonageVideoClient>;
let mockPublisher: Partial<Publisher>;
let handlers: Record<string, (...args: unknown[]) => void>;
const mockPublish = vi.fn();
const mockUnpublish = vi.fn();

beforeEach(() => {
handlers = {};
mockVonageVideoClient = Object.assign(new EventEmitter(), {
on: vi.fn(),
off: vi.fn(),
}) as unknown as Partial<VonageVideoClient> as VonageVideoClient;

mockPublisher = {
on: vi.fn(),
on: vi.fn((event, cb) => {
handlers[event] = cb;
}),
destroy: vi.fn(),
} as unknown as Partial<Publisher>;

Expand Down Expand Up @@ -101,6 +106,164 @@ describe('useScreenSharing', () => {
expect(result.current.isSharingScreen).toBe(false);
});

it('sets isEntireScreen to true when displaySurface is monitor', async () => {
const { result } = render({
userContext: {
__interceptor: (context: UserContextType | null) => {
context!.user.defaultSettings.name = 'TestUser';
},
},
sessionContext: {
__interceptor: (context) => {
if (context) {
context.vonageVideoClient = mockVonageVideoClient as unknown as VonageVideoClient;
context.publish = mockPublish;
context.unpublish = mockUnpublish;
}
},
},
});

await act(async () => {
await result.current.toggleShareScreen();
});

const mockVideoEl = {
srcObject: {
getVideoTracks: () => [{ getSettings: () => ({ displaySurface: 'monitor' }) }],
},
} as unknown as HTMLVideoElement;

act(() => {
handlers['videoElementCreated']({ element: mockVideoEl });
});

expect(result.current.isEntireScreen).toBe(true);
expect(result.current.screenshareVideoElement).toBe(mockVideoEl);
});

it('sets isEntireScreen to false when displaySurface is window', async () => {
const { result } = render({
userContext: {
__interceptor: (context: UserContextType | null) => {
context!.user.defaultSettings.name = 'TestUser';
},
},
sessionContext: {
__interceptor: (context) => {
if (context) {
context.vonageVideoClient = mockVonageVideoClient as unknown as VonageVideoClient;
context.publish = mockPublish;
context.unpublish = mockUnpublish;
}
},
},
});

await act(async () => {
await result.current.toggleShareScreen();
});

const mockVideoEl = {
srcObject: {
getVideoTracks: () => [{ getSettings: () => ({ displaySurface: 'window' }) }],
},
} as unknown as HTMLVideoElement;

act(() => {
handlers['videoElementCreated']({ element: mockVideoEl });
});

expect(result.current.isEntireScreen).toBe(false);
});

it('resets isEntireScreen when streamDestroyed fires', async () => {
const { result } = render({
userContext: {
__interceptor: (context: UserContextType | null) => {
context!.user.defaultSettings.name = 'TestUser';
},
},
sessionContext: {
__interceptor: (context) => {
if (context) {
context.vonageVideoClient = mockVonageVideoClient as unknown as VonageVideoClient;
context.publish = mockPublish;
context.unpublish = mockUnpublish;
}
},
},
});

await act(async () => {
await result.current.toggleShareScreen();
});

const mockVideoEl = {
srcObject: {
getVideoTracks: () => [{ getSettings: () => ({ displaySurface: 'monitor' }) }],
},
} as unknown as HTMLVideoElement;

act(() => {
handlers['videoElementCreated']({ element: mockVideoEl });
});

expect(result.current.screenshareVideoElement).toBe(mockVideoEl);
expect(result.current.isEntireScreen).toBe(true);

act(() => {
handlers['streamDestroyed']();
});

expect(result.current.isEntireScreen).toBe(false);
expect(result.current.isSharingScreen).toBe(false);
expect(result.current.screenshareVideoElement).toBe(undefined);
});

it('sets isEntireScreen to true when displaySurface is undefined but dimensions match the screen area', async () => {
const { result } = render({
userContext: {
__interceptor: (context: UserContextType | null) => {
context!.user.defaultSettings.name = 'TestUser';
},
},
sessionContext: {
__interceptor: (context) => {
if (context) {
context.vonageVideoClient = mockVonageVideoClient as unknown as VonageVideoClient;
context.publish = mockPublish;
context.unpublish = mockUnpublish;
}
},
},
});

await act(async () => {
await result.current.toggleShareScreen();
});

const mockVideoEl = {
srcObject: {
getVideoTracks: () => [
{
getSettings: () => ({
displaySurface: undefined,
width: window.screen.width,
height: window.screen.height,
}),
},
],
},
} as unknown as HTMLVideoElement;

act(() => {
handlers['videoElementCreated']({ element: mockVideoEl });
});

expect(result.current.isEntireScreen).toBe(true);
});

it('does not initialize publisher if session is null', async () => {
const { result } = render({
userContext: {
Expand Down
10 changes: 8 additions & 2 deletions frontend/src/hooks/useMeetingRoom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,13 @@ const useMeetingRoom = () => {
archiveIdStartedBySelf,
} = useSessionContext();

const { isSharingScreen, screensharingPublisher, screenshareVideoElement, toggleShareScreen } =
useScreenShare();
const {
isSharingScreen,
isEntireScreen,
screensharingPublisher,
screenshareVideoElement,
toggleShareScreen,
} = useScreenShare();

const isSmallViewport = useIsSmallViewport();

Expand Down Expand Up @@ -139,6 +144,7 @@ const useMeetingRoom = () => {
t,
isSmallViewport,
isSharingScreen,
isEntireScreen,
screensharingPublisher,
screenshareVideoElement,
toggleShareScreen,
Expand Down
Loading
Loading