Skip to content
Merged
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
50 changes: 16 additions & 34 deletions apps/meteor/client/providers/VideoConfProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import type { CallPreferences, DirectCallData, IRoom, ProviderCapabilities } from '@rocket.chat/core-typings';
import { useToastMessageDispatch, useSetting } from '@rocket.chat/ui-contexts';
import type { VideoConfPopupPayload } from '@rocket.chat/ui-video-conf';
import type { VideoConfPopupPayload, VideoConfContextValue } from '@rocket.chat/ui-video-conf';
import { VideoConfContext } from '@rocket.chat/ui-video-conf';
import type { ReactElement, ReactNode } from 'react';
import { useState, useMemo, useEffect } from 'react';
Expand Down Expand Up @@ -41,40 +40,23 @@ const VideoConfContextProvider = ({ children }: { children: ReactNode }): ReactE
VideoConfManager.on('calling/ended', () => setOutgoing(undefined));
}, []);

const contextValue = useMemo(
const contextValue = useMemo<VideoConfContextValue>(
() => ({
dispatchOutgoing: (option: Omit<VideoConfPopupPayload, 'id'>): void => setOutgoing({ ...option, id: option.rid }),
dismissOutgoing: (): void => setOutgoing(undefined),
startCall: (rid: IRoom['_id'], confTitle?: string): Promise<void> => VideoConfManager.startCall(rid, confTitle),
acceptCall: (callId: string): void => VideoConfManager.acceptIncomingCall(callId),
joinCall: (callId: string): Promise<void> => VideoConfManager.joinCall(callId),
dismissCall: (callId: string): void => {
VideoConfManager.dismissIncomingCall(callId);
},
rejectIncomingCall: (callId: string): void => VideoConfManager.rejectIncomingCall(callId),
abortCall: (): void => VideoConfManager.abortCall(),
setPreferences: (prefs: Partial<(typeof VideoConfManager)['preferences']>): void => VideoConfManager.setPreferences(prefs),
dispatchOutgoing: (option) => setOutgoing({ ...option, id: option.rid }),
dismissOutgoing: () => setOutgoing(undefined),
startCall: (rid, confTitle) => VideoConfManager.startCall(rid, confTitle),
acceptCall: (callId) => VideoConfManager.acceptIncomingCall(callId),
joinCall: (callId) => VideoConfManager.joinCall(callId),
dismissCall: (callId) => VideoConfManager.dismissIncomingCall(callId),
rejectIncomingCall: (callId) => VideoConfManager.rejectIncomingCall(callId),
abortCall: () => VideoConfManager.abortCall(),
setPreferences: (prefs) => VideoConfManager.setPreferences(prefs),
loadCapabilities: VideoConfManager.loadCapabilities,
queryIncomingCalls: {
getSnapshot: (): DirectCallData[] => VideoConfManager.getIncomingDirectCalls(),
subscribe: (cb: () => void) => VideoConfManager.on('incoming/changed', cb),
},
queryRinging: {
getSnapshot: (): boolean => VideoConfManager.isRinging(),
subscribe: (cb: () => void) => VideoConfManager.on('ringing/changed', cb),
},
queryCalling: {
getSnapshot: (): boolean => VideoConfManager.isCalling(),
subscribe: (cb: () => void) => VideoConfManager.on('calling/changed', cb),
},
queryCapabilities: {
getSnapshot: (): ProviderCapabilities => VideoConfManager.capabilities,
subscribe: (cb: () => void) => VideoConfManager.on('capabilities/changed', cb),
},
queryPreferences: {
getSnapshot: (): CallPreferences => VideoConfManager.preferences,
subscribe: (cb: () => void) => VideoConfManager.on('preference/changed', cb),
},
queryIncomingCalls: () => [(cb) => VideoConfManager.on('incoming/changed', cb), () => VideoConfManager.getIncomingDirectCalls()],
queryRinging: () => [(cb) => VideoConfManager.on('ringing/changed', cb), () => VideoConfManager.isRinging()],
queryCalling: () => [(cb) => VideoConfManager.on('calling/changed', cb), () => VideoConfManager.isCalling()],
queryCapabilities: () => [(cb) => VideoConfManager.on('capabilities/changed', cb), () => VideoConfManager.capabilities],
queryPreferences: () => [(cb) => VideoConfManager.on('preference/changed', cb), () => VideoConfManager.preferences],
}),
[],
);
Expand Down
7 changes: 1 addition & 6 deletions apps/meteor/client/sidebarv2/hooks/useRoomList.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,12 +80,7 @@ const getWrapperSettings = ({
<VideoConfContext.Provider
value={
{
queryIncomingCalls: {
subscribe: () => () => undefined,
getSnapshot: () => {
return emptyArr;
},
},
queryIncomingCalls: () => [() => () => undefined, () => emptyArr],
} as any
}
children={children}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { mockAppRoot } from '@rocket.chat/mock-providers';
import { render, screen } from '@testing-library/react';

import VideoConfPopups from './VideoConfPopups';
import { createFakeRoom } from '../../../../../../tests/mocks/data';
import { createFakeVideoConfCall, createFakeIncomingCall } from '../../../../../../tests/mocks/utils/video-conference';

const fakeRoom = createFakeRoom({ t: 'd' });
const fakeDirectVideoConfCall = createFakeVideoConfCall({ type: 'direct', rid: fakeRoom._id });
const fakeIncomingCall = createFakeIncomingCall({ rid: fakeRoom._id });

test('should render video conference incoming popup', async () => {
render(<VideoConfPopups />, {
wrapper: mockAppRoot()
.withRoom(fakeRoom)
.withEndpoint('GET', '/v1/video-conference.info', () => fakeDirectVideoConfCall as any)
.withIncomingCalls([fakeIncomingCall])
.build(),
});

expect(await screen.findByRole('dialog')).toBeInTheDocument();
});
31 changes: 31 additions & 0 deletions apps/meteor/tests/mocks/utils/video-conference.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { faker } from '@faker-js/faker';
import type { IRoom, VideoConferenceType } from '@rocket.chat/core-typings';

const callId = faker.database.mongodbObjectId();
const uid = faker.database.mongodbObjectId();

export function createFakeVideoConfCall({ type, rid }: { type: VideoConferenceType; rid: IRoom['_id'] }) {
return {
type,
rid,
_id: callId,
status: 0,
createdBy: {
_id: uid,
username: faker.internet.userName(),
name: faker.person.fullName(),
},
_updatedAt: faker.date.recent(),
createdAt: faker.date.recent(),
providerName: faker.company.name(),
};
}

export function createFakeIncomingCall({ rid }: { rid: IRoom['_id'] }) {
return {
rid,
uid,
callId,
dismissed: faker.helpers.arrayElement([true, false]),
};
}
1 change: 1 addition & 0 deletions packages/mock-providers/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"devDependencies": {
"@rocket.chat/ddp-client": "workspace:~",
"@rocket.chat/ui-contexts": "workspace:*",
"@rocket.chat/ui-video-conf": "workspace:*",
"@tanstack/react-query": "~5.65.1",
"eslint": "~8.45.0",
"react": "~18.3.1",
Expand Down
123 changes: 108 additions & 15 deletions packages/mock-providers/src/MockedAppRootBuilder.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
import type { ISetting, IUser, Serialized, SettingValue } from '@rocket.chat/core-typings';
import type {
CallPreferences,
DirectCallData,
IRoom,
ISetting,
IUser,
ProviderCapabilities,
Serialized,
SettingValue,
} from '@rocket.chat/core-typings';
import type { ServerMethodName, ServerMethodParameters, ServerMethodReturn } from '@rocket.chat/ddp-client';
import { Emitter } from '@rocket.chat/emitter';
import languages from '@rocket.chat/i18n/dist/languages';
Expand All @@ -15,6 +24,8 @@ import {
ActionManagerContext,
ModalContext,
} from '@rocket.chat/ui-contexts';
import type { VideoConfPopupPayload } from '@rocket.chat/ui-video-conf';
import { VideoConfContext } from '@rocket.chat/ui-video-conf';
import type { Decorator } from '@storybook/react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { createInstance } from 'i18next';
Expand Down Expand Up @@ -89,13 +100,57 @@ export class MockedAppRootBuilder {
private user: ContextType<typeof UserContext> = {
logout: () => Promise.reject(new Error('not implemented')),
queryPreference: () => [() => () => undefined, () => undefined],
queryRoom: () => [() => () => undefined, () => undefined],
queryRoom: () => [() => () => undefined, () => this.room],
querySubscription: () => [() => () => undefined, () => undefined],
querySubscriptions: () => [() => () => undefined, () => this.subscriptions], // apply query and option
user: null,
userId: null,
};

private videoConf: ContextType<typeof VideoConfContext> = {
queryIncomingCalls: () => [() => () => undefined, () => []],
queryRinging: () => [() => () => undefined, () => false],
queryCalling: () => [() => () => undefined, () => false],
dispatchOutgoing(_options: Omit<VideoConfPopupPayload, 'id'>): void {
throw new Error('Function not implemented.');
},
dismissOutgoing(): void {
throw new Error('Function not implemented.');
},
startCall(_rid: IRoom['_id'], _title?: string): void {
throw new Error('Function not implemented.');
},
acceptCall(_callId: string): void {
throw new Error('Function not implemented.');
},
joinCall(_callId: string): void {
throw new Error('Function not implemented.');
},
dismissCall(_callId: string): void {
throw new Error('Function not implemented.');
},
rejectIncomingCall(_callId: string): void {
throw new Error('Function not implemented.');
},
abortCall(): void {
throw new Error('Function not implemented.');
},
setPreferences(_prefs: { mic?: boolean; cam?: boolean }): void {
throw new Error('Function not implemented.');
},
loadCapabilities(): Promise<void> {
throw new Error('Function not implemented.');
},
queryCapabilities(): [subscribe: (onStoreChange: () => void) => () => void, getSnapshot: () => ProviderCapabilities] {
throw new Error('Function not implemented.');
},
queryPreferences(): [subscribe: (onStoreChange: () => void) => () => void, getSnapshot: () => CallPreferences] {
throw new Error('Function not implemented.');
},
};

private room: IRoom | undefined = undefined;

private subscriptions: SubscriptionWithRoom[] = [];

private modal: ModalContextValue = {
Expand Down Expand Up @@ -278,6 +333,12 @@ export class MockedAppRootBuilder {
return this;
}

withRoom(room: IRoom): this {
this.room = room;

return this;
}

withRole(role: string): this {
if (!this.user.user) {
throw new Error('user is not defined');
Expand Down Expand Up @@ -346,6 +407,26 @@ export class MockedAppRootBuilder {
return this;
}

withIncomingCalls(calls: DirectCallData[]): this {
if (!this.videoConf) {
throw Error('videoConf is not defined');
}

const innerFn = this.videoConf.queryIncomingCalls;

const outerFn = (): [subscribe: (onStoreChange: () => void) => () => void, getSnapshot: () => DirectCallData[]] => {
if (calls.length) {
return [() => () => undefined, () => calls];
}

return innerFn();
};

this.videoConf.queryIncomingCalls = outerFn;

return this;
}

withOpenModal(modal: ReactNode) {
this.modal.currentModal = { component: modal };

Expand Down Expand Up @@ -401,7 +482,19 @@ export class MockedAppRootBuilder {
},
});

const { connectionStatus, server, router, settings, user, i18n, authorization, wrappers, audioInputDevices, audioOutputDevices } = this;
const {
connectionStatus,
server,
router,
settings,
user,
videoConf,
i18n,
authorization,
wrappers,
audioInputDevices,
audioOutputDevices,
} = this;

const reduceTranslation = (translation?: ContextType<typeof TranslationContext>): ContextType<typeof TranslationContext> => {
return {
Expand Down Expand Up @@ -499,19 +592,19 @@ export class MockedAppRootBuilder {
notifyIdle: () => undefined,
}}
>
{/* <VideoConfProvider>
<CallProvider>
<VideoConfContext.Provider value={videoConf}>
{/* <CallProvider>
<OmnichannelProvider> */}
{wrappers.reduce<ReactNode>(
(children, wrapper) => wrapper(children),
<>
{children}
{modal.currentModal.component}
</>,
)}
{/* </OmnichannelProvider>
</CallProvider>
</VideoConfProvider>*/}
{wrappers.reduce<ReactNode>(
(children, wrapper) => wrapper(children),
<>
{children}
{modal.currentModal.component}
</>,
)}
{/* </OmnichannelProvider>
</CallProvider> */}
</VideoConfContext.Provider>
</ActionManagerContext.Provider>
{/* </UserPresenceProvider>
</OmnichannelRoomIconProvider>
Expand Down
27 changes: 6 additions & 21 deletions packages/ui-video-conf/src/VideoConfContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export type VideoConfPopupPayload = {
isReceiving?: boolean;
};

type VideoConfContextValue = {
export type VideoConfContextValue = {
dispatchOutgoing: (options: Omit<VideoConfPopupPayload, 'id'>) => void;
dismissOutgoing: () => void;
startCall: (rid: IRoom['_id'], title?: string) => void;
Expand All @@ -18,26 +18,11 @@ type VideoConfContextValue = {
abortCall: () => void;
setPreferences: (prefs: { mic?: boolean; cam?: boolean }) => void;
loadCapabilities: () => Promise<void>;
queryIncomingCalls: {
subscribe: (cb: () => void) => () => void;
getSnapshot: () => DirectCallData[];
};
queryRinging: {
subscribe: (cb: () => void) => () => void;
getSnapshot: () => boolean;
};
queryCalling: {
subscribe: (cb: () => void) => () => void;
getSnapshot: () => boolean;
};
queryCapabilities: {
subscribe: (cb: () => void) => () => void;
getSnapshot: () => ProviderCapabilities;
};
queryPreferences: {
subscribe: (cb: () => void) => () => void;
getSnapshot: () => CallPreferences;
};
queryIncomingCalls: () => [subscribe: (onStoreChange: () => void) => () => void, getSnapshot: () => DirectCallData[]];
queryRinging: () => [subscribe: (onStoreChange: () => void) => () => void, getSnapshot: () => boolean];
queryCalling: () => [subscribe: (onStoreChange: () => void) => () => void, getSnapshot: () => boolean];
queryCapabilities: () => [subscribe: (onStoreChange: () => void) => () => void, getSnapshot: () => ProviderCapabilities];
queryPreferences: () => [subscribe: (onStoreChange: () => void) => () => void, getSnapshot: () => CallPreferences];
};

export const VideoConfContext = createContext<VideoConfContextValue | undefined>(undefined);
Loading
Loading