From c4d9a24ad6af59fb5da70980debca92911da50fa Mon Sep 17 00:00:00 2001 From: Carlos Date: Tue, 5 Sep 2023 08:36:49 -0300 Subject: [PATCH 1/9] test(mocks): update realtime mock --- __mocks__/realtime.mock.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/__mocks__/realtime.mock.ts b/__mocks__/realtime.mock.ts index 43e4d651..217ff4ef 100644 --- a/__mocks__/realtime.mock.ts +++ b/__mocks__/realtime.mock.ts @@ -19,6 +19,7 @@ export const createRealtimeHistory = () => ({ }); export const ABLY_REALTIME_MOCK: AblyRealtimeService = { + isJoinedRoom: false, isLocalParticipantHost: true, setGather: jest.fn(), setHost: jest.fn(), From 32b960461c64e562a32f9529b180ea891340331b Mon Sep 17 00:00:00 2001 From: Carlos Date: Tue, 5 Sep 2023 08:37:49 -0300 Subject: [PATCH 2/9] fix: wait user enter realtime to initialize video --- src/components/video/index.test.ts | 23 ++++++++++++-- src/components/video/index.ts | 50 ++++++++++++++++++++---------- 2 files changed, 55 insertions(+), 18 deletions(-) diff --git a/src/components/video/index.test.ts b/src/components/video/index.test.ts index b93c0d9c..cde71ef7 100644 --- a/src/components/video/index.test.ts +++ b/src/components/video/index.test.ts @@ -62,6 +62,8 @@ jest.mock('../../services/event-bus', () => { return jest.fn().mockImplementation(() => EVENT_BUS_MOCK); }); +const REALTIME_MOCK = Object.assign({}, ABLY_REALTIME_MOCK, { isJoinedRoom: true }); + describe('VideoComponent', () => { let VideoComponentInstance: VideoComponent; @@ -69,8 +71,9 @@ describe('VideoComponent', () => { jest.clearAllMocks(); VideoComponentInstance = new VideoComponent(); + VideoComponentInstance.attach({ - realtime: ABLY_REALTIME_MOCK, + realtime: REALTIME_MOCK, localParticipant: MOCK_LOCAL_PARTICIPANT, group: MOCK_GROUP, config: MOCK_CONFIG, @@ -82,6 +85,22 @@ describe('VideoComponent', () => { VideoComponentInstance.detach(); }); + test('should not initialize video if realtime is not joined room', () => { + VideoComponentInstance.detach(); + + VideoComponentInstance = new VideoComponent(); + + VideoComponentInstance.attach({ + realtime: ABLY_REALTIME_MOCK, + localParticipant: MOCK_LOCAL_PARTICIPANT, + group: MOCK_GROUP, + config: MOCK_CONFIG, + eventBus: EVENT_BUS_MOCK, + }); + + expect(VIDEO_MANAGER_MOCK.start).not.toHaveBeenCalled(); + }); + test('should not show avatar settings if local participant has avatar', () => { VideoComponentInstance.detach(); @@ -90,7 +109,7 @@ describe('VideoComponent', () => { }); VideoComponentInstance.attach({ - realtime: ABLY_REALTIME_MOCK, + realtime: REALTIME_MOCK, localParticipant: { ...MOCK_LOCAL_PARTICIPANT, avatar: MOCK_AVATAR, diff --git a/src/components/video/index.ts b/src/components/video/index.ts index e1880b29..149d07ea 100644 --- a/src/components/video/index.ts +++ b/src/components/video/index.ts @@ -34,7 +34,7 @@ export class VideoComponent extends BaseComponent { private participantToFrameList: ParticipandToFrame[] = []; private participantsOnMeeting: Partial[] = []; - private videoManager: VideoConfereceManager; + private videoManager?: VideoConfereceManager; private connectionService: ConnectionService; private browserService: BrowserService; @@ -62,14 +62,25 @@ export class VideoComponent extends BaseComponent { * @description start video component * @returns {void} */ - protected start(): void { + protected start = (): void => { this.logger.log('video component @ start'); + if (!this.realtime.isJoinedRoom) { + this.logger.log('video component @ start - not joined yet'); + + setTimeout(() => { + this.logger.log('video component @ start - retrying'); + this.start(); + }, 1000); + + return; + } + this.publish(MeetingEvent.MEETING_START); this.suscribeToRealtimeEvents(); this.startVideo(); - } + }; /** * @function destroy @@ -84,7 +95,7 @@ export class VideoComponent extends BaseComponent { this.unsubscribeFromRealtimeEvents(); this.unsubscribeFromVideoEvents(); - this.videoManager.leave(); + this.videoManager?.leave(); this.connectionService.removeListeners(); } @@ -153,19 +164,19 @@ export class VideoComponent extends BaseComponent { private unsubscribeFromVideoEvents = (): void => { this.logger.log('video component @ unsubscribe from video events'); - this.videoManager.meetingConnectionObserver.unsubscribe( + this.videoManager?.meetingConnectionObserver.unsubscribe( this.connectionService.updateMeetingConnectionStatus, ); - this.videoManager.participantListObserver.unsubscribe(this.onParticipantListUpdate); - this.videoManager.waitingForHostObserver.unsubscribe(this.onWaitingForHost); - this.videoManager.frameSizeObserver.unsubscribe(this.onFrameSizeDidChange); - this.videoManager.meetingStateObserver.unsubscribe(this.onMeetingStateChange); - this.videoManager.frameStateObserver.unsubscribe(this.onFrameStateChange); - this.videoManager.realtimeEventsObserver.unsubscribe(this.onRealtimeEventFromFrame); - this.videoManager.participantJoinedObserver.unsubscribe(this.onParticipantJoined); - this.videoManager.participantLeftObserver.unsubscribe(this.onParticipantLeft); - this.videoManager.sameAccountErrorObserver.unsubscribe(this.onSameAccountError); - this.videoManager.devicesObserver.unsubscribe(this.onDevicesChange); + this.videoManager?.participantListObserver.unsubscribe(this.onParticipantListUpdate); + this.videoManager?.waitingForHostObserver.unsubscribe(this.onWaitingForHost); + this.videoManager?.frameSizeObserver.unsubscribe(this.onFrameSizeDidChange); + this.videoManager?.meetingStateObserver.unsubscribe(this.onMeetingStateChange); + this.videoManager?.frameStateObserver.unsubscribe(this.onFrameStateChange); + this.videoManager?.realtimeEventsObserver.unsubscribe(this.onRealtimeEventFromFrame); + this.videoManager?.participantJoinedObserver.unsubscribe(this.onParticipantJoined); + this.videoManager?.participantLeftObserver.unsubscribe(this.onParticipantLeft); + this.videoManager?.sameAccountErrorObserver.unsubscribe(this.onSameAccountError); + this.videoManager?.devicesObserver.unsubscribe(this.onDevicesChange); }; /** @@ -365,6 +376,9 @@ export class VideoComponent extends BaseComponent { this.publish(MeetingEvent.MEETING_PARTICIPANT_JOINED, participant); this.publish(MeetingEvent.MY_PARTICIPANT_JOINED, participant); + this.onRealtimeParticipantsDidChange(this.realtime.getParticipants); + this.onRoomInfoUpdated(this.realtime.roomProperties); + if (this.videoConfig.canUseDefaultAvatars) { this.realtime.updateMyProperties({ avatar: participant.avatar, @@ -442,7 +456,8 @@ export class VideoComponent extends BaseComponent { * */ private onRoomInfoUpdated = (room: AblyRealtimeData): void => { this.logger.log('video component @ on room info updated', room); - const { isGridModeEnable, followParticipantId, gather, drawing, transcript } = room; + const { isGridModeEnable, followParticipantId, gather, drawing, transcript, hostClientId } = + room; this.videoManager.publishMessageToFrame( RealtimeEvent.REALTIME_GRID_MODE_CHANGE, @@ -454,6 +469,7 @@ export class VideoComponent extends BaseComponent { followParticipantId, ); this.videoManager.publishMessageToFrame(RealtimeEvent.REALTIME_TRANSCRIPT_CHANGE, transcript); + this.videoManager.publishMessageToFrame(RealtimeEvent.REALTIME_HOST_CHANGE, hostClientId); if (this.realtime.hostClientId === this.localParticipant.id && gather) { this.realtime.setGather(false); @@ -469,6 +485,8 @@ export class VideoComponent extends BaseComponent { private onRealtimeParticipantsDidChange = ( participants: Record, ): void => { + if (!this.videoManager) return; + this.logger.log('video component @ on participants did change', participants); const participantList: ParticipandToFrame[] = Object.values(participants).map( From 2246b01204e6a1a2c11c8a02ed6c888c30e2d441 Mon Sep 17 00:00:00 2001 From: Carlos Date: Tue, 5 Sep 2023 09:05:00 -0300 Subject: [PATCH 3/9] fix: participant and room null callbacks --- src/components/video/index.test.ts | 9 +++++++++ src/components/video/index.ts | 4 +++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/components/video/index.test.ts b/src/components/video/index.test.ts index cde71ef7..2f19730d 100644 --- a/src/components/video/index.test.ts +++ b/src/components/video/index.test.ts @@ -62,6 +62,8 @@ jest.mock('../../services/event-bus', () => { return jest.fn().mockImplementation(() => EVENT_BUS_MOCK); }); +jest.useFakeTimers(); + const REALTIME_MOCK = Object.assign({}, ABLY_REALTIME_MOCK, { isJoinedRoom: true }); describe('VideoComponent', () => { @@ -98,7 +100,14 @@ describe('VideoComponent', () => { eventBus: EVENT_BUS_MOCK, }); + VideoComponentInstance['start'] = jest.fn(VideoComponentInstance['start']); + VideoComponentInstance['logger'].log = jest.fn(); + expect(VIDEO_MANAGER_MOCK.start).not.toHaveBeenCalled(); + + jest.advanceTimersByTime(1000); + + expect(VideoComponentInstance['start']).toHaveBeenCalledTimes(1); }); test('should not show avatar settings if local participant has avatar', () => { diff --git a/src/components/video/index.ts b/src/components/video/index.ts index 149d07ea..5d7d050a 100644 --- a/src/components/video/index.ts +++ b/src/components/video/index.ts @@ -455,6 +455,8 @@ export class VideoComponent extends BaseComponent { * @returns {void} * */ private onRoomInfoUpdated = (room: AblyRealtimeData): void => { + if (!room) return; + this.logger.log('video component @ on room info updated', room); const { isGridModeEnable, followParticipantId, gather, drawing, transcript, hostClientId } = room; @@ -485,7 +487,7 @@ export class VideoComponent extends BaseComponent { private onRealtimeParticipantsDidChange = ( participants: Record, ): void => { - if (!this.videoManager) return; + if (!this.videoManager || !participants) return; this.logger.log('video component @ on participants did change', participants); From 70123893dd275568ab8da05008297c70bf70ccd1 Mon Sep 17 00:00:00 2001 From: brunokunace Date: Wed, 6 Sep 2023 08:47:19 -0300 Subject: [PATCH 4/9] fix: update presence-mouse component, webcomponent and unit tests --- src/components/presence-mouse/index.test.ts | 201 ++++++++++++++++++++ src/components/presence-mouse/index.ts | 59 ++++-- src/components/presence-mouse/types.ts | 7 +- src/web-components/presence-mouse/index.ts | 37 +++- 4 files changed, 272 insertions(+), 32 deletions(-) diff --git a/src/components/presence-mouse/index.test.ts b/src/components/presence-mouse/index.test.ts index e69de29b..8b005e5e 100644 --- a/src/components/presence-mouse/index.test.ts +++ b/src/components/presence-mouse/index.test.ts @@ -0,0 +1,201 @@ +import { PresenceMouseComponent } from './index'; +import { MeetingEvent } from '../../common/types/events.types'; +import { AblyParticipant } from '../../services/realtime/ably/types'; +import { ABLY_REALTIME_MOCK } from "../../../__mocks__/realtime.mock"; + +describe('PresenceMouseComponent', () => { + let presenceMouseComponent: PresenceMouseComponent; + + beforeEach(() => { + presenceMouseComponent = new PresenceMouseComponent(); + presenceMouseComponent['realtime'] = ABLY_REALTIME_MOCK; + presenceMouseComponent['presenceMouseElement'] = document.createElement('superviz-presence-mouse') + // @ts-ignore + presenceMouseComponent['presenceMouseElement'].updatePresenceMouseParticipant = jest.fn(); + // @ts-ignore + presenceMouseComponent['presenceMouseElement'].removePresenceMouseParticipant = jest.fn(); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + describe('start', () => { + it('should subscribe to realtime events', () => { + presenceMouseComponent['subscribeToRealtimeEvents'] = jest.fn(); + + presenceMouseComponent['start'](); + + expect(presenceMouseComponent['subscribeToRealtimeEvents']).toHaveBeenCalled(); + }); + }); + + describe('destroy', () => { + it('should publish DESTROY event and unsubscribe from realtime events', () => { + presenceMouseComponent['publish'] = jest.fn(); + presenceMouseComponent['unsubscribeFromRealtimeEvents'] = jest.fn(); + + presenceMouseComponent['destroy'](); + + expect(presenceMouseComponent['publish']).toHaveBeenCalledWith(MeetingEvent.DESTROY); + expect(presenceMouseComponent['unsubscribeFromRealtimeEvents']).toHaveBeenCalled(); + }); + + it('should remove event listener and remove presence mouse element from container', () => { + const presenceContainerId = document.createElement('div'); + presenceMouseComponent['containerId'] = 'container'; + document.getElementById = jest.fn().mockReturnValue(presenceContainerId); + + const removeEventListenerSpy = jest.spyOn(presenceContainerId, 'removeEventListener'); + const removeChildSpy = jest.spyOn(presenceContainerId, 'removeChild'); + + presenceMouseComponent['containerId'] = 'container'; + + presenceContainerId.appendChild(presenceMouseComponent['presenceMouseElement']); + + presenceMouseComponent['destroy'](); + + expect(removeEventListenerSpy).toHaveBeenCalledWith('mousemove', presenceMouseComponent['onMyParticipantMouseMove']); + expect(removeChildSpy).toHaveBeenCalledWith(presenceMouseComponent['presenceMouseElement']); + }); + }); + + describe('subscribeToRealtimeEvents', () => { + it('should subscribe to realtime events', () => { + const participantJoinedObserverSubscribeSpy = jest.spyOn(presenceMouseComponent['realtime'].participantJoinedObserver, 'subscribe'); + const participantLeaveObserverSubscribeSpy = jest.spyOn(presenceMouseComponent['realtime'].participantLeaveObserver, 'subscribe'); + const participantsObserverSubscribeSpy = jest.spyOn(presenceMouseComponent['realtime'].participantsObserver, 'subscribe'); + + presenceMouseComponent['subscribeToRealtimeEvents'](); + + expect(participantJoinedObserverSubscribeSpy).toHaveBeenCalledWith(presenceMouseComponent['onParticipantJoinedOnRealtime']); + expect(participantLeaveObserverSubscribeSpy).toHaveBeenCalledWith(presenceMouseComponent['onParticipantLeftOnRealtime']); + expect(participantsObserverSubscribeSpy).toHaveBeenCalledWith(presenceMouseComponent['onParticipantsDidChange']); + }); + }); + + describe('unsubscribeFromRealtimeEvents', () => { + it('should unsubscribe from realtime events', () => { + const participantJoinedObserverUnsubscribeSpy = jest.spyOn(presenceMouseComponent['realtime'].participantJoinedObserver, 'unsubscribe'); + const participantLeaveObserverUnsubscribeSpy = jest.spyOn(presenceMouseComponent['realtime'].participantLeaveObserver, 'unsubscribe'); + const participantsObserverUnsubscribeSpy = jest.spyOn(presenceMouseComponent['realtime'].participantsObserver, 'unsubscribe'); + + presenceMouseComponent['unsubscribeFromRealtimeEvents'](); + + expect(participantJoinedObserverUnsubscribeSpy).toHaveBeenCalledWith(presenceMouseComponent['onParticipantJoinedOnRealtime']); + expect(participantLeaveObserverUnsubscribeSpy).toHaveBeenCalledWith(presenceMouseComponent['onParticipantLeftOnRealtime']); + expect(participantsObserverUnsubscribeSpy).toHaveBeenCalledWith(presenceMouseComponent['onParticipantsDidChange']); + }); + }); + + describe('onMyParticipantMouseMove', () => { + it('should update my participant mouse position', () => { + const updateMyPropertiesSpy = jest.spyOn(presenceMouseComponent['realtime'], 'updateMyProperties'); + const presenceContainerId = document.createElement('div'); + presenceMouseComponent['containerId'] = 'container'; + document.getElementById = jest.fn().mockReturnValue(presenceContainerId); + + const event = { x: 10, y: 20 }; + presenceMouseComponent['onMyParticipantMouseMove'](event); + + expect(updateMyPropertiesSpy).toHaveBeenCalledWith({ + mousePositionX: event.x, + mousePositionY: event.y, + originalWidth: 0, + originalHeight: 0, + containerId: 'container', + }); + }); + }); + + describe('onParticipantsDidChange', () => { + it('should update presence mouse element for external participants', () => { + // @ts-ignore + const updatePresenceMouseParticipantSpy = jest.spyOn(presenceMouseComponent['presenceMouseElement'], 'updatePresenceMouseParticipant'); + + const MOCK_ABLY_PARTICIPANT: AblyParticipant = { + clientId: 'MOCK_LOCAL_PARTICIPANT.id', + action: 'present', + connectionId: 'connection1', + encoding: 'h264', + id: 'unit-test-participant1-ably-id', + timestamp: new Date().getTime(), + data: { + participantId: 'MOCK_LOCAL_PARTICIPANT.id', + mousePositionX: 1, + slotIndex: 0, + }, + } + const participant2 = MOCK_ABLY_PARTICIPANT + participant2.id = 'unit-test-participant2-ably-id' + participant2.data.participantId = 'participant2-id' + + const participants: Record = { + participant: MOCK_ABLY_PARTICIPANT, + participant2: participant2, + }; + + presenceMouseComponent['localParticipant'] = { id: 'unit-test-participant1-ably-id' }; + + presenceMouseComponent['onParticipantsDidChange'](participants); + + expect(updatePresenceMouseParticipantSpy).toHaveBeenCalledWith(participant2.data); + }); + }); + + describe('onParticipantJoinedOnRealtime', () => { + it('should create presence mouse element and add event listener', () => { + const presenceContainerId = document.createElement('div'); + presenceMouseComponent['containerId'] = 'container'; + + const appendChildSpy = jest.spyOn(presenceContainerId, 'appendChild'); + const addEventListenerSpy = jest.spyOn(presenceContainerId, 'addEventListener'); + + document.getElementById = jest.fn().mockReturnValue(presenceContainerId); + + const MOCK_ABLY_PARTICIPANT: AblyParticipant = { + clientId: 'MOCK_LOCAL_PARTICIPANT.id', + action: 'present', + connectionId: 'connection1', + encoding: 'h264', + id: 'unit-test-participant-ably-id', + timestamp: new Date().getTime(), + data: { + id: 'unit-test-participant-ably-id', + slotIndex: 0, + }, + } + + presenceMouseComponent['localParticipant'] = { id: 'unit-test-participant-ably-id' }; + + presenceMouseComponent['onParticipantJoinedOnRealtime'](MOCK_ABLY_PARTICIPANT); + + expect(appendChildSpy).toHaveBeenCalledWith(presenceMouseComponent['presenceMouseElement']); + expect(addEventListenerSpy).toHaveBeenCalledWith('mousemove', presenceMouseComponent['onMyParticipantMouseMove']); + }); + }); + + describe('onParticipantLeftOnRealtime', () => { + it('should remove presence mouse participant', () => { + // @ts-ignore + const removePresenceMouseParticipantSpy = jest.spyOn(presenceMouseComponent['presenceMouseElement'], 'removePresenceMouseParticipant'); + + const MOCK_ABLY_PARTICIPANT: AblyParticipant = { + clientId: 'MOCK_LOCAL_PARTICIPANT.id', + action: 'present', + connectionId: 'connection1', + encoding: 'h264', + id: 'unit-test-participant1-ably-id', + timestamp: new Date().getTime(), + data: { + participantId: 'MOCK_LOCAL_PARTICIPANT.id', + slotIndex: 0, + }, + } + + presenceMouseComponent['onParticipantLeftOnRealtime'](MOCK_ABLY_PARTICIPANT); + + expect(removePresenceMouseParticipantSpy).toHaveBeenCalledWith(MOCK_ABLY_PARTICIPANT.clientId); + }); + }); +}); \ No newline at end of file diff --git a/src/components/presence-mouse/index.ts b/src/components/presence-mouse/index.ts index 317ed8a3..66d2f043 100644 --- a/src/components/presence-mouse/index.ts +++ b/src/components/presence-mouse/index.ts @@ -1,21 +1,21 @@ import { MeetingEvent } from '../../common/types/events.types'; import { Logger } from '../../common/utils'; import { AblyParticipant } from '../../services/realtime/ably/types'; -import { PresenceMouse } from '../../web-components/presence-mouse'; import { BaseComponent } from '../base'; -import { mouseOptions } from './types'; +import { MouseOptions } from './types'; export class PresenceMouseComponent extends BaseComponent { public name: string; protected logger: Logger; - private presenceMouseElement: PresenceMouse; + private presenceMouseElement: HTMLElement; + private containerId: string | null; - constructor() { + constructor(container?: string | null) { super(); - this.name = 'presence-mouse-component'; this.logger = new Logger('@superviz/sdk/presence-mouse-component'); + this.containerId = container ?? null; } /** @@ -26,7 +26,7 @@ export class PresenceMouseComponent extends BaseComponent { protected start(): void { this.logger.log('presence-mouse component @ start'); - this.suscribeToRealtimeEvents(); + this.subscribeToRealtimeEvents(); } /** @@ -41,16 +41,21 @@ export class PresenceMouseComponent extends BaseComponent { this.unsubscribeFromRealtimeEvents(); - window.removeEventListener('mousemove', this.onMyParticipantMouseMove); - document.body.removeChild(this.presenceMouseElement); + const presenceContainerId = this.containerId ? + document.getElementById(this.containerId) : null; + + if (presenceContainerId) { + presenceContainerId.removeEventListener('mousemove', this.onMyParticipantMouseMove); + presenceContainerId.removeChild(this.presenceMouseElement); + } } /** - * @function suscribeToRealtimeEvents + * @function subscribeToRealtimeEvents * @description subscribe to realtime events * @returns {void} */ - private suscribeToRealtimeEvents = (): void => { + private subscribeToRealtimeEvents = (): void => { this.logger.log('presence-mouse component @ subscribe to realtime events'); this.realtime.participantJoinedObserver.subscribe(this.onParticipantJoinedOnRealtime); this.realtime.participantLeaveObserver.subscribe(this.onParticipantLeftOnRealtime); @@ -76,9 +81,17 @@ export class PresenceMouseComponent extends BaseComponent { * @returns {void} */ private onMyParticipantMouseMove = (e): void => { + const presenceContainerId = this.containerId ? + document.getElementById(this.containerId) : document?.body; + + const rect = presenceContainerId.getBoundingClientRect(); + this.realtime.updateMyProperties({ - mousePositionX: e.clientX, - mousePositionY: e.clientY, + mousePositionX: this.containerId ? e.x - rect.x : e.x, + mousePositionY: this.containerId ? e.y - rect.y : e.y, + originalWidth: this.containerId ? rect.width : 1, + originalHeight: this.containerId ? rect.height : 1, + containerId: this.containerId, }); }; @@ -92,15 +105,16 @@ export class PresenceMouseComponent extends BaseComponent { this.logger.log('presence-mouse component @ on participants did change', participants); Object.values(participants).forEach((participant: AblyParticipant) => { - const externalParticipantData: mouseOptions = participant.data; - const hasPresenceMouseElement = - externalParticipantData?.mousePositionX && this.presenceMouseElement; + const externalParticipantData: MouseOptions = participant.data; + const hasPresenceMouseElement = externalParticipantData?.mousePositionX + && this.presenceMouseElement; const myParticipant = externalParticipantData?.id === this.localParticipant?.id; externalParticipantData.color = this.realtime.getSlotColor(participant.data.slotIndex).color; externalParticipantData.slotIndex = participant.data.slotIndex; if (!myParticipant && hasPresenceMouseElement) { + // @ts-ignore this.presenceMouseElement.updatePresenceMouseParticipant(externalParticipantData); } }); @@ -116,11 +130,13 @@ export class PresenceMouseComponent extends BaseComponent { this.logger.log('presence-mouse component @ on participant joined on realtime', participant); if (participant?.data?.id === this.localParticipant?.id) { - this.presenceMouseElement = document.createElement( - 'superviz-presence-mouse', - ) as PresenceMouse; - document.body.appendChild(this.presenceMouseElement); - window.addEventListener('mousemove', this.onMyParticipantMouseMove); + const presenceContainerId = this.containerId ? + document.getElementById(this.containerId) : document?.body; + + this.presenceMouseElement = document.createElement('superviz-presence-mouse'); + + presenceContainerId.appendChild(this.presenceMouseElement); + presenceContainerId.addEventListener('mousemove', this.onMyParticipantMouseMove); } }; @@ -132,6 +148,7 @@ export class PresenceMouseComponent extends BaseComponent { */ private onParticipantLeftOnRealtime = (participant: AblyParticipant): void => { this.logger.log('presence-mouse component @ on participant left on realtime', participant); + // @ts-ignore this.presenceMouseElement.removePresenceMouseParticipant(participant.clientId); }; -} +} \ No newline at end of file diff --git a/src/components/presence-mouse/types.ts b/src/components/presence-mouse/types.ts index 943cada3..c4d0b3b9 100644 --- a/src/components/presence-mouse/types.ts +++ b/src/components/presence-mouse/types.ts @@ -1,8 +1,11 @@ -export interface mouseOptions { +export interface MouseOptions { + containerId?: string; + originalHeight: number; + originalWidth: number; name: string; id: string; color: string; slotIndex: number; mousePositionX: number; mousePositionY: number; -} +} \ No newline at end of file diff --git a/src/web-components/presence-mouse/index.ts b/src/web-components/presence-mouse/index.ts index 6c7fc817..7518c91a 100644 --- a/src/web-components/presence-mouse/index.ts +++ b/src/web-components/presence-mouse/index.ts @@ -1,7 +1,7 @@ import { LitElement, html } from 'lit'; import { customElement } from 'lit/decorators.js'; -import { mouseOptions } from '../../components/presence-mouse/types'; +import { MouseOptions } from '../../components/presence-mouse/types'; import { styles as customMouseStyles } from './styles'; @@ -17,12 +17,12 @@ export class PresenceMouse extends LitElement { } /** - * @function updatePresenceMouse + * @function updatePresenceMouseParticipant * @description handler for update presence mouse change event - * @param {mouseOptions} externalParticipant - presence mouse change data + * @param {MouseOptions} externalParticipant - presence mouse change data * @returns {void} * */ - public updatePresenceMouseParticipant = (externalParticipant: mouseOptions): void => { + public updatePresenceMouseParticipant = (externalParticipant: MouseOptions): void => { const userMouseIdExist = this.shadowRoot.getElementById(`mouse-${externalParticipant.id}`); if (!userMouseIdExist) { const mouseSync = this.shadowRoot.getElementById('mouse-sync'); @@ -51,10 +51,10 @@ export class PresenceMouse extends LitElement { const divPointer = this.shadowRoot.getElementById(`mouse-${externalParticipant.id}`); if (divMouseUser && divPointer) { const mouseUser = divMouseUser.getElementsByClassName( - 'mouse-user-name', + 'mouse-user-name', )[0] as HTMLDivElement; const pointerUser = divPointer.getElementsByClassName( - 'pointer-mouse', + 'pointer-mouse', )[0] as HTMLDivElement; if (pointerUser) { pointerUser.style.backgroundImage = `url(https://production.cdn.superviz.com/static/pointers/${externalParticipant.slotIndex}.svg)`; @@ -64,8 +64,27 @@ export class PresenceMouse extends LitElement { mouseUser.style.backgroundColor = externalParticipant.color; mouseUser.innerHTML = externalParticipant.name; } - divMouseFollower.style.left = `${externalParticipant.mousePositionX.toString()}px`; - divMouseFollower.style.top = `${externalParticipant.mousePositionY.toString()}px`; + + const { containerId } = externalParticipant; + + const presenceContainerId = containerId ? + document.getElementById(containerId) : + document?.body; + + let adjustedX = externalParticipant.mousePositionX; + let adjustedY = externalParticipant.mousePositionY; + + if (containerId) { + const windowWidth = presenceContainerId.clientWidth; + const windowHeight = presenceContainerId.clientHeight; + adjustedX = (externalParticipant.mousePositionX / + externalParticipant.originalWidth) * windowWidth; + adjustedY = (externalParticipant.mousePositionY / + externalParticipant.originalHeight) * windowHeight; + } + + divMouseFollower.style.left = `${adjustedX}px`; + divMouseFollower.style.top = `${adjustedY}px`; } }; @@ -90,4 +109,4 @@ export class PresenceMouse extends LitElement { `; } -} +} \ No newline at end of file From 201e6dfc103ed77e3d52305192cf5e3666bfe79f Mon Sep 17 00:00:00 2001 From: Vinicius Date: Thu, 7 Sep 2023 15:00:13 -0300 Subject: [PATCH 5/9] refactor: add group name to send on activity --- src/core/launcher/index.ts | 4 ++-- src/services/api/index.test.ts | 10 +++++----- src/services/api/index.ts | 9 +++++---- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/core/launcher/index.ts b/src/core/launcher/index.ts index 323f8463..d939d356 100644 --- a/src/core/launcher/index.ts +++ b/src/core/launcher/index.ts @@ -4,6 +4,7 @@ import { ParticipantEvent, RealtimeEvent } from '../../common/types/events.types import { Group, Participant } from '../../common/types/participant.types'; import { Logger } from '../../common/utils'; import { BaseComponent } from '../../components/base'; +import ApiService from '../../services/api'; import config from '../../services/config'; import { EventBus } from '../../services/event-bus'; import { PubSub } from '../../services/pubsub'; @@ -12,7 +13,6 @@ import { AblyParticipant, RealtimeMessage } from '../../services/realtime/ably/t import { HostObserverCallbackResponse } from '../../services/realtime/base/types'; import { DefaultLauncher, LauncherFacade, LauncherOptions } from './types'; -import ApiService from "../../services/api"; export class Launcher implements DefaultLauncher { private readonly shouldKickParticipantsOnHostLeave: boolean; @@ -62,7 +62,7 @@ export class Launcher implements DefaultLauncher { config: config.configuration, eventBus: this.eventBus, }); - ApiService.sendActivity(this.participant.id, this.group.id, component.name) + ApiService.sendActivity(this.participant.id, this.group.id, this.group.name, component.name); }; /** diff --git a/src/services/api/index.test.ts b/src/services/api/index.test.ts index b49d3491..9f347480 100644 --- a/src/services/api/index.test.ts +++ b/src/services/api/index.test.ts @@ -79,11 +79,11 @@ describe('ApiService', () => { describe('sendActivity', () => { test('should return any message', async () => { - - const userId = 'user-id' - const groupId = 'group-id' - const product = 'video-component' - const response = await ApiService.sendActivity(userId, groupId, product); + const userId = 'user-id'; + const groupId = 'group-id'; + const groupName = 'group-name'; + const product = 'video-component'; + const response = await ApiService.sendActivity(userId, groupId, groupName, product); expect(response).toEqual({ message: 'any message' }); }); diff --git a/src/services/api/index.ts b/src/services/api/index.ts index 7bb19f25..6949cc53 100644 --- a/src/services/api/index.ts +++ b/src/services/api/index.ts @@ -1,5 +1,5 @@ import { doRequest } from '../../common/utils'; -import config from "../config"; +import config from '../config'; export default class ApiService { static createUrl(baseUrl: string, path: string): string { @@ -25,7 +25,7 @@ export default class ApiService { return message; } - static async sendActivity(userId: string, groupId: string, product: string) { + static async sendActivity(userId: string, groupId: string, groupName: string, product: string) { const path = '/activity'; const baseUrl = config.get('apiUrl'); const meetingId = config.get('roomId'); @@ -33,10 +33,11 @@ export default class ApiService { const url = this.createUrl(baseUrl, path); const body = { groupId, + groupName, meetingId, product, - userId - } + userId, + }; return doRequest(url, 'POST', body, { apikey }); } } From 74721048e8960c77b33d1a83b6baab1143b13e63 Mon Sep 17 00:00:00 2001 From: brunokunace Date: Mon, 11 Sep 2023 11:02:27 -0300 Subject: [PATCH 6/9] fix: create webcomponent presence mouse tests --- src/components/presence-mouse/index.test.ts | 2 +- .../presence-mouse/index.test.ts | 101 ++++++++++++++++++ src/web-components/presence-mouse/index.ts | 4 +- 3 files changed, 104 insertions(+), 3 deletions(-) diff --git a/src/components/presence-mouse/index.test.ts b/src/components/presence-mouse/index.test.ts index 2f830105..e5dfdaf4 100644 --- a/src/components/presence-mouse/index.test.ts +++ b/src/components/presence-mouse/index.test.ts @@ -180,7 +180,7 @@ describe('PresenceMouseComponent', () => { it('should remove presence mouse participant', () => { // @ts-ignore const removePresenceMouseParticipantSpy = jest.spyOn(presenceMouseComponent['presenceMouseElement'], 'removePresenceMouseParticipant'); - + const MOCK_ABLY_PARTICIPANT: AblyParticipant = { clientId: 'MOCK_LOCAL_PARTICIPANT.id', action: 'present', diff --git a/src/web-components/presence-mouse/index.test.ts b/src/web-components/presence-mouse/index.test.ts index e69de29b..5a8aacc8 100644 --- a/src/web-components/presence-mouse/index.test.ts +++ b/src/web-components/presence-mouse/index.test.ts @@ -0,0 +1,101 @@ +import { PresenceMouse } from './index'; +import { MouseOptions } from '../../components/presence-mouse/types'; +import sleep from "../../common/utils/sleep"; + +describe('PresenceMouse', () => { + let presenceMouse: PresenceMouse; + + beforeEach(async () => { + presenceMouse = new PresenceMouse(); + document.body.appendChild(presenceMouse); + await sleep(); + }); + + describe('updatePresenceMouseParticipant', () => { + it('should add a new mouse follower element if it does not exist', () => { + const externalParticipant: MouseOptions = { + id: 'participant1', + name: 'participant', + slotIndex: 1, + color: '#FF0000', + mousePositionX: 10, + mousePositionY: 20, + originalWidth: 100, + originalHeight: 100, + containerId: 'container', + }; + + const renderedElement = document.getElementsByTagName('superviz-presence-mouse')[0]; + + renderedElement.shadowRoot.getElementById = jest.fn().mockReturnValue(null); + + presenceMouse.updatePresenceMouseParticipant(externalParticipant); + + expect(renderedElement.shadowRoot.getElementById).toHaveBeenCalledWith(`mouse-${externalParticipant.id}`); + }); + + it('should update the existing mouse follower element if it exists', () => { + const externalParticipant: MouseOptions = { + id: 'participant1', + name: 'participant', + slotIndex: 1, + color: '#FF0000', + mousePositionX: 10, + mousePositionY: 20, + originalWidth: 100, + originalHeight: 100, + containerId: 'container', + }; + + const mouseFollowerElement = document.createElement('div'); + mouseFollowerElement.setAttribute('id', `mouse-${externalParticipant.id}`); + mouseFollowerElement.setAttribute('class', 'mouse-follower'); + + const mouseUserNameElement = document.createElement('div'); + mouseUserNameElement.setAttribute('class', 'mouse-user-name'); + mouseUserNameElement.innerHTML = externalParticipant.name; + + const renderedElement = document.getElementsByTagName('superviz-presence-mouse')[0]; + + renderedElement.shadowRoot.getElementById = jest.fn().mockReturnValue(mouseFollowerElement); + mouseFollowerElement.getElementsByClassName = jest.fn().mockReturnValue([mouseUserNameElement]); + + presenceMouse.updatePresenceMouseParticipant(externalParticipant); + + expect(renderedElement.shadowRoot.getElementById).toHaveBeenCalledWith(`mouse-${externalParticipant.id}`); + expect(mouseUserNameElement.style.color).toBe('rgb(0, 0, 0)'); + expect(mouseUserNameElement.style.backgroundColor).toBe('rgb(255, 0, 0)'); + expect(mouseUserNameElement.innerHTML).toBe(externalParticipant.name); + expect(mouseFollowerElement.style.left).toBe('0.1px'); + expect(mouseFollowerElement.style.top).toBe('0.2px'); + }); + }); + + describe('removePresenceMouseParticipant', () => { + it('should remove the mouse follower element if it exists', () => { + const participantId = 'participant1'; + const mouseFollowerElement = document.createElement('div'); + + const renderedElement = document.getElementsByTagName('superviz-presence-mouse')[0]; + + renderedElement.shadowRoot.getElementById = jest.fn().mockReturnValue(mouseFollowerElement); + mouseFollowerElement.remove = jest.fn(); + + presenceMouse.removePresenceMouseParticipant(participantId); + + expect(renderedElement.shadowRoot.getElementById).toHaveBeenCalledWith(`mouse-${participantId}`); + expect(mouseFollowerElement.remove).toHaveBeenCalled(); + }); + + it('should do nothing if the mouse follower element does not exist', () => { + const participantId = 'participant1'; + + const renderedElement = document.getElementsByTagName('superviz-presence-mouse')[0]; + renderedElement.shadowRoot.getElementById = jest.fn().mockReturnValue(null); + + presenceMouse.removePresenceMouseParticipant(participantId); + + expect(renderedElement.shadowRoot.getElementById).toHaveBeenCalledWith(`mouse-${participantId}`); + }); + }); +}); \ No newline at end of file diff --git a/src/web-components/presence-mouse/index.ts b/src/web-components/presence-mouse/index.ts index 67620c1e..d770ff4f 100644 --- a/src/web-components/presence-mouse/index.ts +++ b/src/web-components/presence-mouse/index.ts @@ -75,8 +75,8 @@ export class PresenceMouse extends LitElement { let adjustedY = externalParticipant.mousePositionY; if (containerId) { - const windowWidth = presenceContainerId.clientWidth; - const windowHeight = presenceContainerId.clientHeight; + const windowWidth = presenceContainerId?.clientWidth || 1; + const windowHeight = presenceContainerId?.clientHeight || 1; adjustedX = (externalParticipant.mousePositionX / externalParticipant.originalWidth) * windowWidth; adjustedY = (externalParticipant.mousePositionY / From 3a87572fd927c1358495ca5262b504759b1dff87 Mon Sep 17 00:00:00 2001 From: vitorvargasdev Date: Tue, 12 Sep 2023 13:35:39 -0300 Subject: [PATCH 7/9] Revert "Merge remote-tracking branch 'origin/lab' into fix/presence-mouse-fix-test-runner" This reverts commit de1513d5f0dc72fdf7054dae12b2f62fa8651258, reversing changes made to 74721048e8960c77b33d1a83b6baab1143b13e63. --- __mocks__/realtime.mock.ts | 1 - src/components/video/index.test.ts | 32 ++---------------- src/components/video/index.ts | 52 +++++++++--------------------- src/core/launcher/index.ts | 4 +-- src/services/api/index.test.ts | 10 +++--- src/services/api/index.ts | 9 +++--- 6 files changed, 29 insertions(+), 79 deletions(-) diff --git a/__mocks__/realtime.mock.ts b/__mocks__/realtime.mock.ts index 217ff4ef..43e4d651 100644 --- a/__mocks__/realtime.mock.ts +++ b/__mocks__/realtime.mock.ts @@ -19,7 +19,6 @@ export const createRealtimeHistory = () => ({ }); export const ABLY_REALTIME_MOCK: AblyRealtimeService = { - isJoinedRoom: false, isLocalParticipantHost: true, setGather: jest.fn(), setHost: jest.fn(), diff --git a/src/components/video/index.test.ts b/src/components/video/index.test.ts index 2f19730d..b93c0d9c 100644 --- a/src/components/video/index.test.ts +++ b/src/components/video/index.test.ts @@ -62,10 +62,6 @@ jest.mock('../../services/event-bus', () => { return jest.fn().mockImplementation(() => EVENT_BUS_MOCK); }); -jest.useFakeTimers(); - -const REALTIME_MOCK = Object.assign({}, ABLY_REALTIME_MOCK, { isJoinedRoom: true }); - describe('VideoComponent', () => { let VideoComponentInstance: VideoComponent; @@ -73,9 +69,8 @@ describe('VideoComponent', () => { jest.clearAllMocks(); VideoComponentInstance = new VideoComponent(); - VideoComponentInstance.attach({ - realtime: REALTIME_MOCK, + realtime: ABLY_REALTIME_MOCK, localParticipant: MOCK_LOCAL_PARTICIPANT, group: MOCK_GROUP, config: MOCK_CONFIG, @@ -87,29 +82,6 @@ describe('VideoComponent', () => { VideoComponentInstance.detach(); }); - test('should not initialize video if realtime is not joined room', () => { - VideoComponentInstance.detach(); - - VideoComponentInstance = new VideoComponent(); - - VideoComponentInstance.attach({ - realtime: ABLY_REALTIME_MOCK, - localParticipant: MOCK_LOCAL_PARTICIPANT, - group: MOCK_GROUP, - config: MOCK_CONFIG, - eventBus: EVENT_BUS_MOCK, - }); - - VideoComponentInstance['start'] = jest.fn(VideoComponentInstance['start']); - VideoComponentInstance['logger'].log = jest.fn(); - - expect(VIDEO_MANAGER_MOCK.start).not.toHaveBeenCalled(); - - jest.advanceTimersByTime(1000); - - expect(VideoComponentInstance['start']).toHaveBeenCalledTimes(1); - }); - test('should not show avatar settings if local participant has avatar', () => { VideoComponentInstance.detach(); @@ -118,7 +90,7 @@ describe('VideoComponent', () => { }); VideoComponentInstance.attach({ - realtime: REALTIME_MOCK, + realtime: ABLY_REALTIME_MOCK, localParticipant: { ...MOCK_LOCAL_PARTICIPANT, avatar: MOCK_AVATAR, diff --git a/src/components/video/index.ts b/src/components/video/index.ts index 5d7d050a..e1880b29 100644 --- a/src/components/video/index.ts +++ b/src/components/video/index.ts @@ -34,7 +34,7 @@ export class VideoComponent extends BaseComponent { private participantToFrameList: ParticipandToFrame[] = []; private participantsOnMeeting: Partial[] = []; - private videoManager?: VideoConfereceManager; + private videoManager: VideoConfereceManager; private connectionService: ConnectionService; private browserService: BrowserService; @@ -62,25 +62,14 @@ export class VideoComponent extends BaseComponent { * @description start video component * @returns {void} */ - protected start = (): void => { + protected start(): void { this.logger.log('video component @ start'); - if (!this.realtime.isJoinedRoom) { - this.logger.log('video component @ start - not joined yet'); - - setTimeout(() => { - this.logger.log('video component @ start - retrying'); - this.start(); - }, 1000); - - return; - } - this.publish(MeetingEvent.MEETING_START); this.suscribeToRealtimeEvents(); this.startVideo(); - }; + } /** * @function destroy @@ -95,7 +84,7 @@ export class VideoComponent extends BaseComponent { this.unsubscribeFromRealtimeEvents(); this.unsubscribeFromVideoEvents(); - this.videoManager?.leave(); + this.videoManager.leave(); this.connectionService.removeListeners(); } @@ -164,19 +153,19 @@ export class VideoComponent extends BaseComponent { private unsubscribeFromVideoEvents = (): void => { this.logger.log('video component @ unsubscribe from video events'); - this.videoManager?.meetingConnectionObserver.unsubscribe( + this.videoManager.meetingConnectionObserver.unsubscribe( this.connectionService.updateMeetingConnectionStatus, ); - this.videoManager?.participantListObserver.unsubscribe(this.onParticipantListUpdate); - this.videoManager?.waitingForHostObserver.unsubscribe(this.onWaitingForHost); - this.videoManager?.frameSizeObserver.unsubscribe(this.onFrameSizeDidChange); - this.videoManager?.meetingStateObserver.unsubscribe(this.onMeetingStateChange); - this.videoManager?.frameStateObserver.unsubscribe(this.onFrameStateChange); - this.videoManager?.realtimeEventsObserver.unsubscribe(this.onRealtimeEventFromFrame); - this.videoManager?.participantJoinedObserver.unsubscribe(this.onParticipantJoined); - this.videoManager?.participantLeftObserver.unsubscribe(this.onParticipantLeft); - this.videoManager?.sameAccountErrorObserver.unsubscribe(this.onSameAccountError); - this.videoManager?.devicesObserver.unsubscribe(this.onDevicesChange); + this.videoManager.participantListObserver.unsubscribe(this.onParticipantListUpdate); + this.videoManager.waitingForHostObserver.unsubscribe(this.onWaitingForHost); + this.videoManager.frameSizeObserver.unsubscribe(this.onFrameSizeDidChange); + this.videoManager.meetingStateObserver.unsubscribe(this.onMeetingStateChange); + this.videoManager.frameStateObserver.unsubscribe(this.onFrameStateChange); + this.videoManager.realtimeEventsObserver.unsubscribe(this.onRealtimeEventFromFrame); + this.videoManager.participantJoinedObserver.unsubscribe(this.onParticipantJoined); + this.videoManager.participantLeftObserver.unsubscribe(this.onParticipantLeft); + this.videoManager.sameAccountErrorObserver.unsubscribe(this.onSameAccountError); + this.videoManager.devicesObserver.unsubscribe(this.onDevicesChange); }; /** @@ -376,9 +365,6 @@ export class VideoComponent extends BaseComponent { this.publish(MeetingEvent.MEETING_PARTICIPANT_JOINED, participant); this.publish(MeetingEvent.MY_PARTICIPANT_JOINED, participant); - this.onRealtimeParticipantsDidChange(this.realtime.getParticipants); - this.onRoomInfoUpdated(this.realtime.roomProperties); - if (this.videoConfig.canUseDefaultAvatars) { this.realtime.updateMyProperties({ avatar: participant.avatar, @@ -455,11 +441,8 @@ export class VideoComponent extends BaseComponent { * @returns {void} * */ private onRoomInfoUpdated = (room: AblyRealtimeData): void => { - if (!room) return; - this.logger.log('video component @ on room info updated', room); - const { isGridModeEnable, followParticipantId, gather, drawing, transcript, hostClientId } = - room; + const { isGridModeEnable, followParticipantId, gather, drawing, transcript } = room; this.videoManager.publishMessageToFrame( RealtimeEvent.REALTIME_GRID_MODE_CHANGE, @@ -471,7 +454,6 @@ export class VideoComponent extends BaseComponent { followParticipantId, ); this.videoManager.publishMessageToFrame(RealtimeEvent.REALTIME_TRANSCRIPT_CHANGE, transcript); - this.videoManager.publishMessageToFrame(RealtimeEvent.REALTIME_HOST_CHANGE, hostClientId); if (this.realtime.hostClientId === this.localParticipant.id && gather) { this.realtime.setGather(false); @@ -487,8 +469,6 @@ export class VideoComponent extends BaseComponent { private onRealtimeParticipantsDidChange = ( participants: Record, ): void => { - if (!this.videoManager || !participants) return; - this.logger.log('video component @ on participants did change', participants); const participantList: ParticipandToFrame[] = Object.values(participants).map( diff --git a/src/core/launcher/index.ts b/src/core/launcher/index.ts index d939d356..323f8463 100644 --- a/src/core/launcher/index.ts +++ b/src/core/launcher/index.ts @@ -4,7 +4,6 @@ import { ParticipantEvent, RealtimeEvent } from '../../common/types/events.types import { Group, Participant } from '../../common/types/participant.types'; import { Logger } from '../../common/utils'; import { BaseComponent } from '../../components/base'; -import ApiService from '../../services/api'; import config from '../../services/config'; import { EventBus } from '../../services/event-bus'; import { PubSub } from '../../services/pubsub'; @@ -13,6 +12,7 @@ import { AblyParticipant, RealtimeMessage } from '../../services/realtime/ably/t import { HostObserverCallbackResponse } from '../../services/realtime/base/types'; import { DefaultLauncher, LauncherFacade, LauncherOptions } from './types'; +import ApiService from "../../services/api"; export class Launcher implements DefaultLauncher { private readonly shouldKickParticipantsOnHostLeave: boolean; @@ -62,7 +62,7 @@ export class Launcher implements DefaultLauncher { config: config.configuration, eventBus: this.eventBus, }); - ApiService.sendActivity(this.participant.id, this.group.id, this.group.name, component.name); + ApiService.sendActivity(this.participant.id, this.group.id, component.name) }; /** diff --git a/src/services/api/index.test.ts b/src/services/api/index.test.ts index 9f347480..b49d3491 100644 --- a/src/services/api/index.test.ts +++ b/src/services/api/index.test.ts @@ -79,11 +79,11 @@ describe('ApiService', () => { describe('sendActivity', () => { test('should return any message', async () => { - const userId = 'user-id'; - const groupId = 'group-id'; - const groupName = 'group-name'; - const product = 'video-component'; - const response = await ApiService.sendActivity(userId, groupId, groupName, product); + + const userId = 'user-id' + const groupId = 'group-id' + const product = 'video-component' + const response = await ApiService.sendActivity(userId, groupId, product); expect(response).toEqual({ message: 'any message' }); }); diff --git a/src/services/api/index.ts b/src/services/api/index.ts index 6949cc53..7bb19f25 100644 --- a/src/services/api/index.ts +++ b/src/services/api/index.ts @@ -1,5 +1,5 @@ import { doRequest } from '../../common/utils'; -import config from '../config'; +import config from "../config"; export default class ApiService { static createUrl(baseUrl: string, path: string): string { @@ -25,7 +25,7 @@ export default class ApiService { return message; } - static async sendActivity(userId: string, groupId: string, groupName: string, product: string) { + static async sendActivity(userId: string, groupId: string, product: string) { const path = '/activity'; const baseUrl = config.get('apiUrl'); const meetingId = config.get('roomId'); @@ -33,11 +33,10 @@ export default class ApiService { const url = this.createUrl(baseUrl, path); const body = { groupId, - groupName, meetingId, product, - userId, - }; + userId + } return doRequest(url, 'POST', body, { apikey }); } } From 91b36241ce424b697838da0bb93f4b15b8962c4f Mon Sep 17 00:00:00 2001 From: vitorvargasdev Date: Wed, 13 Sep 2023 08:02:17 -0300 Subject: [PATCH 8/9] fix: presence tests --- __mocks__/participants.mock.ts | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/__mocks__/participants.mock.ts b/__mocks__/participants.mock.ts index 994d964e..d78d8a46 100644 --- a/__mocks__/participants.mock.ts +++ b/__mocks__/participants.mock.ts @@ -1,5 +1,5 @@ import { Avatar, Group, Participant, ParticipantType } from '../src'; -import { AblyParticipant } from "../src/services/realtime/ably/types"; +import { AblyParticipant } from '../src/services/realtime/ably/types'; export const MOCK_AVATAR: Avatar = { model: 'unit-test-avatar-model.glb', @@ -10,7 +10,6 @@ export const MOCK_LOCAL_PARTICIPANT: Participant = { id: 'unit-test-local-participant-id', name: 'unit-test-local-participant-name', color: '#000', - type: ParticipantType.HOST, }; export const MOCK_GROUP: Group = { @@ -19,14 +18,14 @@ export const MOCK_GROUP: Group = { }; export const MOCK_ABLY_PARTICIPANT: AblyParticipant = { - clientId: MOCK_LOCAL_PARTICIPANT.id, - action: 'present', - connectionId: 'connection1', - encoding: 'h264', - id: 'unit-test-participant1-ably-id', - timestamp: new Date().getTime(), - data: { - participantId: MOCK_LOCAL_PARTICIPANT.id, - slotIndex: 0, - }, -} + clientId: MOCK_LOCAL_PARTICIPANT.id, + action: 'present', + connectionId: 'connection1', + encoding: 'h264', + id: 'unit-test-participant1-ably-id', + timestamp: new Date().getTime(), + data: { + participantId: MOCK_LOCAL_PARTICIPANT.id, + slotIndex: 0, + }, +}; From 9644bc7c9e61511ba2bee7649d51036a28433cf6 Mon Sep 17 00:00:00 2001 From: vitorvargasdev Date: Wed, 13 Sep 2023 10:07:44 -0300 Subject: [PATCH 9/9] fix: presence web components --- .../presence-mouse/index.test.ts | 175 +++++++++--------- src/web-components/presence-mouse/index.ts | 14 +- web-test-runner.config.js | 2 +- 3 files changed, 97 insertions(+), 94 deletions(-) diff --git a/src/web-components/presence-mouse/index.test.ts b/src/web-components/presence-mouse/index.test.ts index 5a8aacc8..e8e30aca 100644 --- a/src/web-components/presence-mouse/index.test.ts +++ b/src/web-components/presence-mouse/index.test.ts @@ -1,101 +1,104 @@ -import { PresenceMouse } from './index'; import { MouseOptions } from '../../components/presence-mouse/types'; -import sleep from "../../common/utils/sleep"; +import '.'; -describe('PresenceMouse', () => { - let presenceMouse: PresenceMouse; +const createEl = async () => { + const element = document.createElement('superviz-presence-mouse'); + document.body.appendChild(element); + await element['updateComplete']; + return element; +}; - beforeEach(async () => { - presenceMouse = new PresenceMouse(); - document.body.appendChild(presenceMouse); - await sleep(); +describe('PresenceMouse', () => { + afterEach(() => { + document.body.querySelector('superviz-presence-mouse') + ?.remove(); + }); + + describe('updatePresenceMouseParticipant', () => { + it('should add a new mouse follower element if it does not exist', async () => { + const renderedElement = await createEl(); + const externalParticipant: MouseOptions = { + id: 'participant1', + name: 'participant', + slotIndex: 1, + color: '#FF0000', + mousePositionX: 10, + mousePositionY: 20, + originalWidth: 100, + originalHeight: 100, + containerId: 'container', + }; + + renderedElement!.shadowRoot!.getElementById = jest.fn().mockReturnValue(null); + + renderedElement['updatePresenceMouseParticipant'](externalParticipant); + + expect(renderedElement!.shadowRoot!.getElementById).toHaveBeenCalledWith(`mouse-${externalParticipant.id}`); }); - describe('updatePresenceMouseParticipant', () => { - it('should add a new mouse follower element if it does not exist', () => { - const externalParticipant: MouseOptions = { - id: 'participant1', - name: 'participant', - slotIndex: 1, - color: '#FF0000', - mousePositionX: 10, - mousePositionY: 20, - originalWidth: 100, - originalHeight: 100, - containerId: 'container', - }; - - const renderedElement = document.getElementsByTagName('superviz-presence-mouse')[0]; - - renderedElement.shadowRoot.getElementById = jest.fn().mockReturnValue(null); - - presenceMouse.updatePresenceMouseParticipant(externalParticipant); - - expect(renderedElement.shadowRoot.getElementById).toHaveBeenCalledWith(`mouse-${externalParticipant.id}`); - }); - - it('should update the existing mouse follower element if it exists', () => { - const externalParticipant: MouseOptions = { - id: 'participant1', - name: 'participant', - slotIndex: 1, - color: '#FF0000', - mousePositionX: 10, - mousePositionY: 20, - originalWidth: 100, - originalHeight: 100, - containerId: 'container', - }; - - const mouseFollowerElement = document.createElement('div'); - mouseFollowerElement.setAttribute('id', `mouse-${externalParticipant.id}`); - mouseFollowerElement.setAttribute('class', 'mouse-follower'); - - const mouseUserNameElement = document.createElement('div'); - mouseUserNameElement.setAttribute('class', 'mouse-user-name'); - mouseUserNameElement.innerHTML = externalParticipant.name; - - const renderedElement = document.getElementsByTagName('superviz-presence-mouse')[0]; - - renderedElement.shadowRoot.getElementById = jest.fn().mockReturnValue(mouseFollowerElement); - mouseFollowerElement.getElementsByClassName = jest.fn().mockReturnValue([mouseUserNameElement]); - - presenceMouse.updatePresenceMouseParticipant(externalParticipant); - - expect(renderedElement.shadowRoot.getElementById).toHaveBeenCalledWith(`mouse-${externalParticipant.id}`); - expect(mouseUserNameElement.style.color).toBe('rgb(0, 0, 0)'); - expect(mouseUserNameElement.style.backgroundColor).toBe('rgb(255, 0, 0)'); - expect(mouseUserNameElement.innerHTML).toBe(externalParticipant.name); - expect(mouseFollowerElement.style.left).toBe('0.1px'); - expect(mouseFollowerElement.style.top).toBe('0.2px'); - }); + it('should update the existing mouse follower element if it exists', async () => { + const renderedElement = await createEl(); + const externalParticipant: MouseOptions = { + id: 'participant1', + name: 'participant', + slotIndex: 1, + color: '#FF0000', + mousePositionX: 10, + mousePositionY: 20, + originalWidth: 100, + originalHeight: 100, + containerId: 'container', + }; + + const mouseFollowerElement = document.createElement('div'); + mouseFollowerElement.setAttribute('id', `mouse-${externalParticipant.id}`); + mouseFollowerElement.setAttribute('class', 'mouse-follower'); + + const mouseUserNameElement = document.createElement('div'); + mouseUserNameElement.setAttribute('class', 'mouse-user-name'); + mouseUserNameElement.innerHTML = externalParticipant.name; + + renderedElement!.shadowRoot!.getElementById = + jest.fn().mockReturnValue(mouseFollowerElement); + mouseFollowerElement!.getElementsByClassName = + jest.fn().mockReturnValue([mouseUserNameElement]); + + renderedElement['updatePresenceMouseParticipant'](externalParticipant); + + expect(renderedElement!.shadowRoot!.getElementById).toHaveBeenCalledWith(`mouse-${externalParticipant.id}`); + expect(mouseUserNameElement.style.color).toBe('rgb(0, 0, 0)'); + expect(mouseUserNameElement.style.backgroundColor).toBe('rgb(255, 0, 0)'); + expect(mouseUserNameElement.innerHTML).toBe(externalParticipant.name); + expect(mouseFollowerElement.style.left).toBe('0.1px'); + expect(mouseFollowerElement.style.top).toBe('0.2px'); }); + }); - describe('removePresenceMouseParticipant', () => { - it('should remove the mouse follower element if it exists', () => { - const participantId = 'participant1'; - const mouseFollowerElement = document.createElement('div'); + describe('removePresenceMouseParticipant', () => { + it('should remove the mouse follower element if it exists', async () => { + const renderedElement = await createEl(); + const participantId = 'participant1'; + const mouseFollowerElement = document.createElement('div'); - const renderedElement = document.getElementsByTagName('superviz-presence-mouse')[0]; + renderedElement!.shadowRoot!.getElementById = + jest.fn().mockReturnValue(mouseFollowerElement); + mouseFollowerElement.remove = jest.fn(); - renderedElement.shadowRoot.getElementById = jest.fn().mockReturnValue(mouseFollowerElement); - mouseFollowerElement.remove = jest.fn(); + renderedElement['removePresenceMouseParticipant'](participantId); - presenceMouse.removePresenceMouseParticipant(participantId); - - expect(renderedElement.shadowRoot.getElementById).toHaveBeenCalledWith(`mouse-${participantId}`); - expect(mouseFollowerElement.remove).toHaveBeenCalled(); - }); + expect(renderedElement!.shadowRoot!.getElementById).toHaveBeenCalledWith(`mouse-${participantId}`); + expect(mouseFollowerElement.remove).toHaveBeenCalled(); + }); - it('should do nothing if the mouse follower element does not exist', () => { - const participantId = 'participant1'; + it('should do nothing if the mouse follower element does not exist', async () => { + const renderedElement = await createEl(); + const participantId = 'participant1'; - const renderedElement = document.getElementsByTagName('superviz-presence-mouse')[0]; - renderedElement.shadowRoot.getElementById = jest.fn().mockReturnValue(null); + renderedElement!.shadowRoot!.getElementById = jest.fn().mockReturnValue(null); - presenceMouse.removePresenceMouseParticipant(participantId); + renderedElement['removePresenceMouseParticipant'](participantId); - expect(renderedElement.shadowRoot.getElementById).toHaveBeenCalledWith(`mouse-${participantId}`); - }); + expect(renderedElement!.shadowRoot!.getElementById).toHaveBeenCalledWith(`mouse-${participantId}`); }); -}); \ No newline at end of file + }); +}); diff --git a/src/web-components/presence-mouse/index.ts b/src/web-components/presence-mouse/index.ts index d770ff4f..60c1302f 100644 --- a/src/web-components/presence-mouse/index.ts +++ b/src/web-components/presence-mouse/index.ts @@ -51,10 +51,10 @@ export class PresenceMouse extends LitElement { const divPointer = this.shadowRoot.getElementById(`mouse-${externalParticipant.id}`); if (divMouseUser && divPointer) { const mouseUser = divMouseUser.getElementsByClassName( - 'mouse-user-name', + 'mouse-user-name', )[0] as HTMLDivElement; const pointerUser = divPointer.getElementsByClassName( - 'pointer-mouse', + 'pointer-mouse', )[0] as HTMLDivElement; if (pointerUser) { pointerUser.style.backgroundImage = `url(https://production.cdn.superviz.com/static/pointers/${externalParticipant.slotIndex}.svg)`; @@ -68,8 +68,8 @@ export class PresenceMouse extends LitElement { const { containerId } = externalParticipant; const presenceContainerId = containerId ? - document.getElementById(containerId) : - document?.body; + document.getElementById(containerId) : + document?.body; let adjustedX = externalParticipant.mousePositionX; let adjustedY = externalParticipant.mousePositionY; @@ -103,10 +103,10 @@ export class PresenceMouse extends LitElement { protected render() { return html` -
-
+
+
+
-
`; } } diff --git a/web-test-runner.config.js b/web-test-runner.config.js index feb85575..96e47148 100644 --- a/web-test-runner.config.js +++ b/web-test-runner.config.js @@ -45,7 +45,7 @@ module.exports = { functions: 70, lines: 70, }, - include: ['src/web-components/**/*.{ts}'], + include: ['src/web-components/**/*.ts'], }, testRunnerHtml: (testFramework) => `