Skip to content
This repository has been archived by the owner on Oct 18, 2024. It is now read-only.

Commit

Permalink
Merge pull request #290 from SuperViz/feat/presence-mouse
Browse files Browse the repository at this point in the history
feat: presence mouse
  • Loading branch information
brunokunace authored Sep 13, 2023
2 parents 6948a40 + 28333f5 commit 99e1079
Show file tree
Hide file tree
Showing 17 changed files with 695 additions and 81 deletions.
17 changes: 15 additions & 2 deletions __mocks__/participants.mock.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Avatar, Group, Participant, ParticipantType } from '../src/common/types/participant.types';
import { Avatar, Group, Participant, ParticipantType } from '../src';
import { AblyParticipant } from '../src/services/realtime/ably/types';

export const MOCK_AVATAR: Avatar = {
model: 'unit-test-avatar-model.glb',
Expand All @@ -9,10 +10,22 @@ 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 = {
id: 'unit-test-group-id',
name: 'unit-test-group-name',
};

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,
},
};
7 changes: 7 additions & 0 deletions __mocks__/presence-mouse.mock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { MouseOptions } from "../src/components/presence-mouse/types";
import {PresenceMouse} from "../src/web-components";

export const PRESENCE_MOUSE_MOCK: PresenceMouse = <PresenceMouse> {
updatePresenceMouseParticipant: (externalParticipant: MouseOptions): void => {},
removePresenceMouseParticipant: (participantId: string): void => {}
}
1 change: 0 additions & 1 deletion __mocks__/realtime.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ export const createRealtimeHistory = () => ({
});

export const ABLY_REALTIME_MOCK: AblyRealtimeService = {
isJoinedRoom: false,
isLocalParticipantHost: true,
setGather: jest.fn(),
setHost: jest.fn(),
Expand Down
5 changes: 5 additions & 0 deletions src/common/types/participant.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ export interface Participant {
color?: string;
avatar?: Avatar;
isHost?: boolean;
mousePositionX?: number;
mousePositionY?: number;
originalWidth?: number,
originalHeight?: number,
containerId?: string,
// @NOTE - this is a hack to make the participant info work with the 3D avatar
avatarConfig?: unknown;
}
Expand Down
1 change: 1 addition & 0 deletions src/components/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { VideoComponent } from './video';
export { PresenceMouseComponent } from './presence-mouse';
209 changes: 209 additions & 0 deletions src/components/presence-mouse/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
import { MOCK_CONFIG } from '../../../__mocks__/config.mock';
import { EVENT_BUS_MOCK } from '../../../__mocks__/event-bus.mock';
import { MOCK_GROUP, MOCK_LOCAL_PARTICIPANT } from '../../../__mocks__/participants.mock';
import { ABLY_REALTIME_MOCK } from '../../../__mocks__/realtime.mock';
import { MeetingEvent } from '../../common/types/events.types';
import { AblyParticipant } from '../../services/realtime/ably/types';

import { PresenceMouseComponent } from './index';

describe('PresenceMouseComponent', () => {
let presenceMouseComponent: PresenceMouseComponent;

beforeEach(() => {
presenceMouseComponent = new PresenceMouseComponent();

presenceMouseComponent.attach({
realtime: ABLY_REALTIME_MOCK,
localParticipant: MOCK_LOCAL_PARTICIPANT,
group: MOCK_GROUP,
config: MOCK_CONFIG,
eventBus: EVENT_BUS_MOCK,
});

presenceMouseComponent['presenceMouseElement'] = document.createElement('superviz-presence-mouse');
presenceMouseComponent['presenceMouseElement']['updatePresenceMouseParticipant'] = jest.fn();
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', () => {
presenceMouseComponent['presenceMouseElement']['updatePresenceMouseParticipant'] = jest.fn();
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<string, AblyParticipant> = {
participant: MOCK_ABLY_PARTICIPANT,
participant2,
};

presenceMouseComponent['localParticipant'] = { id: 'unit-test-participant1-ably-id' };

presenceMouseComponent['onParticipantsDidChange'](participants);

expect(presenceMouseComponent['presenceMouseElement']['updatePresenceMouseParticipant']).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', () => {
presenceMouseComponent['presenceMouseElement']['removePresenceMouseParticipant'] = jest.fn();
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(presenceMouseComponent['presenceMouseElement']['removePresenceMouseParticipant'])
.toHaveBeenCalledWith(MOCK_ABLY_PARTICIPANT.clientId);
});
});
});
Loading

0 comments on commit 99e1079

Please sign in to comment.