diff --git a/packages/react-components/src/components/TogetherModeOverlay.tsx b/packages/react-components/src/components/TogetherModeOverlay.tsx index 2ee44ecc342..986998c9d67 100644 --- a/packages/react-components/src/components/TogetherModeOverlay.tsx +++ b/packages/react-components/src/components/TogetherModeOverlay.tsx @@ -84,6 +84,14 @@ export const TogetherModeOverlay = memo( [key: string]: TogetherModeParticipantStatus; }>({}); const [hoveredParticipantID, setHoveredParticipantID] = useState(''); + const [tabbedParticipantID, setTabbedParticipantID] = useState(''); + + // Reset the Tab key tracking on any other key press + const handleKeyUp = (e: React.KeyboardEvent, participantId: string) => { + if (e.key === 'Tab') { + setTabbedParticipantID(participantId); + } + }; /* * The useMemo hook is used to calculate the participant status for the Together Mode overlay. @@ -96,7 +104,6 @@ export const TogetherModeOverlay = memo( const participantsWithVideoAvailable = allParticipants.filter( (p) => p.videoStream?.isAvailable && togetherModeSeatPositions[p.userId] ); - const updatedSignals: { [key: string]: TogetherModeParticipantStatus } = {}; for (const p of participantsWithVideoAvailable) { const { userId, reaction, raisedHand, spotlight, isMuted, displayName } = p; @@ -109,7 +116,12 @@ export const TogetherModeOverlay = memo( isSpotlighted: !!spotlight, isMuted, displayName: displayName || locale.strings.videoGallery.displayNamePlaceholder, - showDisplayName: !!(spotlight || raisedHand || hoveredParticipantID === userId), + showDisplayName: !!( + spotlight || + raisedHand || + hoveredParticipantID === userId || + tabbedParticipantID === userId + ), scaledSize: calculateScaledSize(seatingPosition.width, seatingPosition.height), seatPositionStyle: setTogetherModeSeatPositionStyle(seatingPosition) }; @@ -141,7 +153,8 @@ export const TogetherModeOverlay = memo( togetherModeSeatPositions, reactionResources, locale.strings.videoGallery.displayNamePlaceholder, - hoveredParticipantID + hoveredParticipantID, + tabbedParticipantID ]); useEffect(() => { @@ -165,6 +178,9 @@ export const TogetherModeOverlay = memo( }} onMouseEnter={() => setHoveredParticipantID(participantStatus.id)} onMouseLeave={() => setHoveredParticipantID('')} + onKeyUp={(e) => handleKeyUp(e, participantStatus.id)} + onBlur={() => setTabbedParticipantID('')} + tabIndex={0} >
{participantStatus.showDisplayName && ( @@ -210,6 +226,7 @@ export const TogetherModeOverlay = memo( // Second div - Responsible for ensuring the sprite emoji is always centered in the participant seat position // Third div - Play Animation as the other animation applies on the base play animation for the sprite
root?.querySelector('[data-ui-id="responsive-vertical-gallery"]') ?? null; /* @conditional-compile-remove(together-mode) */ -const getTogetherModeLayout = (root: Element | null): Element | null => - root?.querySelector('[data-ui-id="together-mode-layout"]') ?? null; - -const getTiles = (root: Element | null): Element[] => - Array.from(root?.querySelectorAll('[data-ui-id="video-tile"]') ?? []); +Array.from(root?.querySelectorAll('[data-ui-id="video-tile"]') ?? []); const getGridTiles = (root: Element | null): Element[] => Array.from(getTiles(getGridLayout(root))); const tileIsVideo = (tile: Element | undefined): boolean => !!tile?.querySelector('video'); const tileIsAudio = (tile: Element | undefined): boolean => !!tile && !tile.querySelector('video'); diff --git a/packages/react-components/src/components/VideoGallery.tsx b/packages/react-components/src/components/VideoGallery.tsx index 966a2544dc0..073707b09e9 100644 --- a/packages/react-components/src/components/VideoGallery.tsx +++ b/packages/react-components/src/components/VideoGallery.tsx @@ -493,7 +493,6 @@ export const VideoGallery = (props: VideoGalleryProps): JSX.Element => { onForbidVideo, onPermitVideo } = props; - const ids = useIdentifiers(); const theme = useTheme(); const localeStrings = useLocale().strings.videoGallery; @@ -810,8 +809,12 @@ export const VideoGallery = (props: VideoGalleryProps): JSX.Element => { : undefined; /* @conditional-compile-remove(together-mode) */ - const togetherModeStreamComponent = useMemo( - () => ( + const togetherModeStreamComponent = useMemo(() => { + if (layout !== 'togetherMode' || screenShareComponent) { + return null; + } + + return ( { containerWidth={containerWidth} containerHeight={containerHeight} /> - ), - [ - startTogetherModeEnabled, - isTogetherModeActive, - onCreateTogetherModeStreamView, - onStartTogetherMode, - onDisposeTogetherModeStreamView, - onSetTogetherModeSceneSize, - togetherModeStreams, - togetherModeSeatingCoordinates, - localParticipant, - remoteParticipants, - reactionResources, - containerWidth, - containerHeight - ] - ); + ); + }, [ + layout, + screenShareComponent, + startTogetherModeEnabled, + isTogetherModeActive, + onCreateTogetherModeStreamView, + onStartTogetherMode, + onDisposeTogetherModeStreamView, + onSetTogetherModeSceneSize, + togetherModeStreams, + togetherModeSeatingCoordinates, + localParticipant, + remoteParticipants, + reactionResources, + containerWidth, + containerHeight + ]); /* @conditional-compile-remove(together-mode) */ // Current implementation of capabilities is only based on user role. // This logic checks for the user role and if the user is a Teams user. @@ -906,7 +910,7 @@ export const VideoGallery = (props: VideoGalleryProps): JSX.Element => { /* @conditional-compile-remove(together-mode) */ // Teams users can switch to Together mode layout only if they have the capability, // while ACS users can do so only if Together mode is enabled. - if (!screenShareComponent && layout === 'togetherMode' && canSwitchToTogetherModeLayout) { + if (layout === 'togetherMode' && togetherModeStreamComponent && canSwitchToTogetherModeLayout) { return ; } return ; @@ -914,7 +918,6 @@ export const VideoGallery = (props: VideoGalleryProps): JSX.Element => { /* @conditional-compile-remove(together-mode) */ canSwitchToTogetherModeLayout, layout, layoutProps, - screenShareComponent, screenShareParticipant, /* @conditional-compile-remove(together-mode) */ togetherModeStreamComponent ]); diff --git a/packages/react-composites/src/composites/CallComposite/MockCallAdapter.ts b/packages/react-composites/src/composites/CallComposite/MockCallAdapter.ts index ae2003367bf..a5825bd428c 100644 --- a/packages/react-composites/src/composites/CallComposite/MockCallAdapter.ts +++ b/packages/react-composites/src/composites/CallComposite/MockCallAdapter.ts @@ -120,7 +120,7 @@ export class _MockCallAdapter implements CallAdapter { } /* @conditional-compile-remove(together-mode) */ setTogetherModeSceneSize(width: number, height: number): void { - throw Error(`Setting Together Mode scene to width ${width} and height ${height} is not implemented`); + return; } disposeStreamView(): Promise { return Promise.resolve(); diff --git a/packages/react-composites/src/composites/CallComposite/selectors/baseSelectors.ts b/packages/react-composites/src/composites/CallComposite/selectors/baseSelectors.ts index c0d2b58069a..aa41bb0d0b9 100644 --- a/packages/react-composites/src/composites/CallComposite/selectors/baseSelectors.ts +++ b/packages/react-composites/src/composites/CallComposite/selectors/baseSelectors.ts @@ -350,7 +350,6 @@ export const getIsTogetherModeActive = (state: CallAdapterState): boolean | unde * @returns The local participant's user id or undefined. */ export const getLocalUserId = (state: CallAdapterState): CommunicationIdentifierKind | undefined => state.userId; - /** @private */ export const getMediaAccessSetting = (state: CallAdapterState): MediaAccess | undefined => state.call?.meetingMediaAccess; diff --git a/packages/react-composites/tests/app/lib/MockCallAdapter.ts b/packages/react-composites/tests/app/lib/MockCallAdapter.ts index 9a46040ba17..d3c39710d37 100644 --- a/packages/react-composites/tests/app/lib/MockCallAdapter.ts +++ b/packages/react-composites/tests/app/lib/MockCallAdapter.ts @@ -125,7 +125,7 @@ export class MockCallAdapter implements CallAdapter { } /* @conditional-compile-remove(together-mode) */ setTogetherModeSceneSize(width: number, height: number): void { - throw Error(`Setting Together Mode width ${width} and height: ${height} not implemented`); + return; } /* @conditional-compile-remove(together-mode) */ disposeTogetherModeStreamView(): Promise { diff --git a/packages/react-composites/tests/browser/call/hermetic/TogetherModeOverlay.test.ts b/packages/react-composites/tests/browser/call/hermetic/TogetherModeOverlay.test.ts new file mode 100644 index 00000000000..6073c8882c2 --- /dev/null +++ b/packages/react-composites/tests/browser/call/hermetic/TogetherModeOverlay.test.ts @@ -0,0 +1,266 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { + // addScreenshareStream, + addTogetherModeStream, + addVideoStream, + buildUrlWithMockAdapter, + defaultMockCallAdapterState, + defaultMockRemoteParticipant, + test +} from './fixture'; +import { expect } from '@playwright/test'; +import { + dataUiId, + // dragToRight, + // existsOnPage, + isTestProfileMobile, + pageClick, + stableScreenshot, + waitForSelector +} from '../../common/utils'; +import { IDS } from '../../common/constants'; +import { CallKind } from '@azure/communication-calling'; +// import type { MockCallState } from '../../../common'; + +/* @conditional-compile-remove(together-mode) */ +test.describe('Confirm Start Together layout view option ', async () => { + test('Confirm together mode is not shown in ACS Call', async ({ page, serverUrl }, testInfo) => { + test.skip(isTestProfileMobile(testInfo)); + // Remote Participants is ACS Identity + const vasily = defaultMockRemoteParticipant('Vasily'); + const paul = defaultMockRemoteParticipant('Paul'); + const participants = [vasily, paul]; + addVideoStream(vasily, true); + addVideoStream(paul, true); + // Local Participant is ACS Identity + const initialState = defaultMockCallAdapterState(participants); + if (initialState.call) { + initialState.call.togetherMode.isActive = true; + } + await page.goto( + buildUrlWithMockAdapter(serverUrl, initialState, { + newControlBarExperience: 'true' + }) + ); + + await waitForSelector(page, dataUiId(IDS.moreButton)); + await pageClick(page, dataUiId(IDS.moreButton)); + await page.locator('button:has-text("View")').click(); + expect(await stableScreenshot(page)).toMatchSnapshot('together-mode-view-option-hidden-in-acs-call.png'); + }); + + test('Confirm together mode is enabled for Teams Call', async ({ page, serverUrl }, testInfo) => { + test.skip(isTestProfileMobile(testInfo)); + // Remote Participant is Teams Identity + const vasily = defaultMockRemoteParticipant('Vasily', true); + const paul = defaultMockRemoteParticipant('Paul', true); + const participants = [vasily, paul]; + addVideoStream(vasily, true); + addVideoStream(paul, true); + // Local Participant is Teams Identity + const initialState = defaultMockCallAdapterState(participants); + initialState.userId = { kind: 'microsoftTeamsUser', microsoftTeamsUserId: `8:orgid:localUser` }; + initialState.isTeamsCall = true; + if (initialState.call) { + initialState.call.togetherMode.isActive = true; + initialState.call.kind = 'TeamsCall' as CallKind; + } + await page.goto( + buildUrlWithMockAdapter(serverUrl, initialState, { + newControlBarExperience: 'true' + }) + ); + + await waitForSelector(page, dataUiId(IDS.moreButton)); + await pageClick(page, dataUiId(IDS.moreButton)); + await page.locator('button:has-text("View")').click(); + expect(await stableScreenshot(page)).toMatchSnapshot('together-mode-view-option-in-teams-call.png'); + }); + + test('Confirm together mode is enabled for Teams Meeting', async ({ page, serverUrl }, testInfo) => { + test.skip(isTestProfileMobile(testInfo)); + // Remote Participant is Teams Identity + const vasily = defaultMockRemoteParticipant('Vasily', true); + const paul = defaultMockRemoteParticipant('Paul', true); + const participants = [vasily, paul]; + addVideoStream(vasily, true); + addVideoStream(paul, true); + // Local Participant is Teams Identity + const initialState = defaultMockCallAdapterState(participants); + initialState.userId = { kind: 'microsoftTeamsUser', microsoftTeamsUserId: `8:orgid:localUser` }; + initialState.isTeamsMeeting = true; + if (initialState.call) { + initialState.call.togetherMode.isActive = true; + initialState.call.kind = 'TeamsCall' as CallKind; + } + await page.goto( + buildUrlWithMockAdapter(serverUrl, initialState, { + newControlBarExperience: 'true' + }) + ); + + await waitForSelector(page, dataUiId(IDS.moreButton)); + await pageClick(page, dataUiId(IDS.moreButton)); + await page.locator('button:has-text("View")').click(); + expect(await stableScreenshot(page)).toMatchSnapshot('together-mode-view-option-in-teams-meeting.png'); + }); +}); + +test.describe('Confirm Together Mode Stream signaling events', async () => { + test('Confirm raiseHand icon and display Name in together mode layout', async ({ page, serverUrl }, testInfo) => { + test.skip(isTestProfileMobile(testInfo)); + const paul = defaultMockRemoteParticipant('Paul Bridges', true); + const vasily = defaultMockRemoteParticipant('Vasily'); + const participants = [paul, vasily]; + addVideoStream(vasily, true); + vasily.raisedHand = { raisedHandOrderPosition: 1 }; + addVideoStream(vasily, true); + const initialState = defaultMockCallAdapterState(participants); + + if (initialState.call?.togetherMode) { + initialState.call.togetherMode.isActive = true; + addTogetherModeStream(initialState.call.togetherMode.streams, true); + initialState.call.togetherMode.seatingPositions = { + '8:acs:Vasily-id': { left: 0, top: 0, width: 200, height: 200 } + }; + } + initialState.isTeamsCall = true; + if (initialState.call) { + initialState.call.kind = 'TeamsCall' as CallKind; + } + await page.goto( + buildUrlWithMockAdapter(serverUrl, initialState, { + newControlBarExperience: 'true' + }) + ); + + const id = `together-mode-participant-8:acs:Vasily-id`; + await waitForSelector(page, dataUiId(IDS.moreButton)); + await pageClick(page, dataUiId(IDS.moreButton)); + await page.locator('button:has-text("View")').click(); + await page.locator('button:has-text("Together mode")').click(); + await waitForSelector(page, dataUiId(IDS.togetherModeStream)); + await waitForSelector(page, dataUiId(id)); + expect(await stableScreenshot(page)).toMatchSnapshot('together-mode-view-raisehand-icon.png'); + }); + + test('Confirm spotlight icon and display Name in together mode layout', async ({ page, serverUrl }, testInfo) => { + test.skip(isTestProfileMobile(testInfo)); + const paul = defaultMockRemoteParticipant('Paul Bridges', true); + const vasily = defaultMockRemoteParticipant('Vasily'); + const participants = [paul, vasily]; + addVideoStream(vasily, true); + vasily.spotlight = { spotlightedOrderPosition: 1 }; + addVideoStream(vasily, true); + const initialState = defaultMockCallAdapterState(participants); + + if (initialState.call?.togetherMode) { + initialState.call.togetherMode.isActive = true; + addTogetherModeStream(initialState.call.togetherMode.streams, true); + initialState.call.togetherMode.seatingPositions = { + '8:acs:Vasily-id': { left: 0, top: 0, width: 200, height: 200 } + }; + } + initialState.isTeamsCall = true; + if (initialState.call) { + initialState.call.kind = 'TeamsCall' as CallKind; + } + await page.goto( + buildUrlWithMockAdapter(serverUrl, initialState, { + newControlBarExperience: 'true' + }) + ); + + const id = `together-mode-participant-8:acs:Vasily-id`; + await waitForSelector(page, dataUiId(IDS.moreButton)); + await pageClick(page, dataUiId(IDS.moreButton)); + await page.locator('button:has-text("View")').click(); + await page.locator('button:has-text("Together mode")').click(); + await waitForSelector(page, dataUiId(IDS.togetherModeStream)); + await waitForSelector(page, dataUiId(id)); + expect(await stableScreenshot(page)).toMatchSnapshot('together-mode-view-spotlight-icon.png'); + }); + + test('Confirm mute icon and display Name in together mode layout', async ({ page, serverUrl }, testInfo) => { + test.skip(isTestProfileMobile(testInfo)); + const paul = defaultMockRemoteParticipant('Paul Bridges', true); + const vasily = defaultMockRemoteParticipant('Vasily'); + const participants = [paul, vasily]; + addVideoStream(vasily, true); + vasily.spotlight = { spotlightedOrderPosition: 1 }; + vasily.isMuted = true; + addVideoStream(vasily, true); + const initialState = defaultMockCallAdapterState(participants); + + if (initialState.call?.togetherMode) { + initialState.call.togetherMode.isActive = true; + addTogetherModeStream(initialState.call.togetherMode.streams, true); + initialState.call.togetherMode.seatingPositions = { + '8:acs:Vasily-id': { left: 0, top: 0, width: 200, height: 200 } + }; + } + initialState.isTeamsCall = true; + if (initialState.call) { + initialState.call.kind = 'TeamsCall' as CallKind; + } + await page.goto( + buildUrlWithMockAdapter(serverUrl, initialState, { + newControlBarExperience: 'true' + }) + ); + + const id = `together-mode-participant-8:acs:Vasily-id`; + await waitForSelector(page, dataUiId(IDS.moreButton)); + await pageClick(page, dataUiId(IDS.moreButton)); + await page.locator('button:has-text("View")').click(); + await page.locator('button:has-text("Together mode")').click(); + await waitForSelector(page, dataUiId(IDS.togetherModeStream)); + await waitForSelector(page, dataUiId(id)); + expect(await stableScreenshot(page)).toMatchSnapshot('together-mode-view-mute-icon.png'); + }); + + test('Confirm only icons show when seating width is 100px in together mode layout', async ({ + page, + serverUrl + }, testInfo) => { + test.skip(isTestProfileMobile(testInfo)); + const paul = defaultMockRemoteParticipant('Paul Bridges', true); + const vasily = defaultMockRemoteParticipant('Vasily'); + const participants = [paul, vasily]; + addVideoStream(vasily, true); + vasily.spotlight = { spotlightedOrderPosition: 1 }; + vasily.isMuted = true; + vasily.raisedHand = { raisedHandOrderPosition: 1 }; + addVideoStream(vasily, true); + const initialState = defaultMockCallAdapterState(participants); + + if (initialState.call?.togetherMode) { + initialState.call.togetherMode.isActive = true; + addTogetherModeStream(initialState.call.togetherMode.streams, true); + initialState.call.togetherMode.seatingPositions = { + '8:acs:Vasily-id': { left: 0, top: 0, width: 100, height: 100 } + }; + } + initialState.isTeamsCall = true; + if (initialState.call) { + initialState.call.kind = 'TeamsCall' as CallKind; + } + + await page.goto( + buildUrlWithMockAdapter(serverUrl, initialState, { + newControlBarExperience: 'true' + }) + ); + + const id = `together-mode-participant-8:acs:Vasily-id`; + await waitForSelector(page, dataUiId(IDS.moreButton)); + await pageClick(page, dataUiId(IDS.moreButton)); + await page.locator('button:has-text("View")').click(); + await page.locator('button:has-text("Together mode")').click(); + await waitForSelector(page, dataUiId(IDS.togetherModeStream)); + await waitForSelector(page, dataUiId(id)); + expect(await stableScreenshot(page)).toMatchSnapshot('together-mode-view-icons-only.png'); + }); +}); diff --git a/packages/react-composites/tests/browser/call/hermetic/VideoGallery.test.ts b/packages/react-composites/tests/browser/call/hermetic/VideoGallery.test.ts index 016e09fc7d2..c3a5cb1b00c 100644 --- a/packages/react-composites/tests/browser/call/hermetic/VideoGallery.test.ts +++ b/packages/react-composites/tests/browser/call/hermetic/VideoGallery.test.ts @@ -3,6 +3,7 @@ import { addScreenshareStream, + addTogetherModeStream, addVideoStream, buildUrlWithMockAdapter, defaultMockCallAdapterState, diff --git a/packages/react-composites/tests/browser/call/hermetic/fixture.ts b/packages/react-composites/tests/browser/call/hermetic/fixture.ts index c96685fe223..2b3de90a43f 100644 --- a/packages/react-composites/tests/browser/call/hermetic/fixture.ts +++ b/packages/react-composites/tests/browser/call/hermetic/fixture.ts @@ -14,7 +14,12 @@ import type { } from '../../../common'; import type { CallKind, DominantSpeakersInfo, ParticipantRole } from '@azure/communication-calling'; import type { ParticipantCapabilities } from '@azure/communication-calling'; -import { CallState, CapabilitiesFeatureState } from '@internal/calling-stateful-client'; +import { + CallFeatureStreamState, + CallState, + CapabilitiesFeatureState, + TogetherModeStreamsState +} from '@internal/calling-stateful-client'; const SERVER_URL = 'http://localhost'; const APP_DIR = path.join(__dirname, '../../../app/call'); @@ -56,7 +61,8 @@ export function defaultMockCallAdapterState( role?: ParticipantRole, isRoomsCall?: boolean, callEndReasonSubCode?: number, - isReactionCapability?: boolean + isReactionCapability?: boolean, + isTeamsUser?: boolean ): MockCallAdapterState { const remoteParticipants: Record = {}; participants?.forEach((p) => { @@ -94,7 +100,7 @@ export function defaultMockCallAdapterState( /* @conditional-compile-remove(together-mode) */ togetherMode: { isActive: false, streams: {}, seatingPositions: {} }, pptLive: { isActive: false }, - role: role ?? 'Unknown', + role: 'Presenter', dominantSpeakers: dominantSpeakers, totalParticipantCount: Object.values(remoteParticipants).length > 0 ? Object.values(remoteParticipants).length + 1 : undefined, @@ -133,7 +139,9 @@ export function defaultMockCallAdapterState( } } : undefined, - userId: { kind: 'communicationUser', communicationUserId: '1' }, + userId: isTeamsUser + ? { kind: 'microsoftTeamsUser', microsoftTeamsUserId: '8:orgid:localUser' } + : { kind: 'communicationUser', communicationUserId: '8:orgid:localUser' }, devices: { isSpeakerSelectionAvailable: true, selectedCamera: { id: 'camera1', name: '1st Camera', deviceType: 'UsbCamera' }, @@ -166,9 +174,14 @@ export function defaultMockCallAdapterState( * * Use this to add participants to state created via {@link defaultCallAdapterState}. */ -export function defaultMockRemoteParticipant(displayName?: string): MockRemoteParticipantState { +export function defaultMockRemoteParticipant( + displayName?: string, + isTeamsUser: boolean = false +): MockRemoteParticipantState { return { - identifier: { kind: 'communicationUser', communicationUserId: `8:acs:${displayName}-id` }, + identifier: isTeamsUser + ? { kind: 'microsoftTeamsUser', microsoftTeamsUserId: `8:orgid:${displayName}-id` } + : { kind: 'communicationUser', communicationUserId: `8:acs:${displayName}-id` }, state: 'Connected', videoStreams: { 1: { @@ -262,6 +275,32 @@ export function addScreenshareStream( addDummyView(streams[0], isReceiving, scalingMode); } +/** + * Add a screenshare stream to {@link MockRemoteParticipantState}. + * + * Use to add video to participant created via {@link defaultMockRemoteParticipant}. + */ +export function addTogetherModeStream( + togetherModeStreamState: TogetherModeStreamsState, + isReceiving: boolean, + scalingMode?: 'Stretch' | 'Crop' | 'Fit' +): void { + const togetherModeStream = + togetherModeStreamState.mainVideoStream || + ({ + feature: 'togetherMode', + mediaStreamType: 'Video', + isAvailable: true, + isReceiving: true + } as CallFeatureStreamState); + // if (!togetherModeStream) { + // throw new Error(`Expected togetherMode Stream to be active`); + // } + if (togetherModeStreamState.mainVideoStream) { + addDummyView(togetherModeStreamState.mainVideoStream, isReceiving, scalingMode); + } +} + /** * Add a dummy view to a stream that will be replaced by an actual {@link HTMLElement} by the test app. * diff --git a/packages/react-composites/tests/browser/common/constants.ts b/packages/react-composites/tests/browser/common/constants.ts index 046fca0955d..cec9f8ee4d3 100644 --- a/packages/react-composites/tests/browser/common/constants.ts +++ b/packages/react-composites/tests/browser/common/constants.ts @@ -44,7 +44,8 @@ export const IDS = { reactionButtonSubMenu: 'reaction-sub-menu', reactionMobileDrawerMenuItem: 'reaction-mobile-drawer-menu-item', cameraButton: 'call-composite-camera-button', - microphoneButton: 'call-composite-microphone-button' + microphoneButton: 'call-composite-microphone-button', + togetherModeStream: 'together-mode-layout' }; export const spokenLanguageStrings = [ diff --git a/packages/react-composites/tests/common/MockCallAdapterState.ts b/packages/react-composites/tests/common/MockCallAdapterState.ts index 644af3fdd58..0519d33d733 100644 --- a/packages/react-composites/tests/common/MockCallAdapterState.ts +++ b/packages/react-composites/tests/common/MockCallAdapterState.ts @@ -2,6 +2,7 @@ // Licensed under the MIT License. import { + CallFeatureStreamState, CallState, LocalVideoStreamState, RemoteParticipantState, @@ -84,6 +85,14 @@ export interface MockRemoteVideoStreamState extends RemoteVideoStreamState { dummyView?: MockVideoStreamRendererViewState; } +export interface MockFeatureVideoStreamState extends CallFeatureStreamState { + /** + * Dummy placeholder for `view`. + * The test application creates a `view` corresponding to the + * `dummyView` generating an HTMLElement for the target if needed. + */ + dummyView?: MockVideoStreamRendererViewState; +} /** * A slight modification of {@link VideoStreamRendererViewState} for initializing the * {@link MockCallAdapter} for hermetic e2e tests.