import { Chat, getProtocols } from '../src/chat'; import { mock, instance, resetCalls, verify, anything, capture } from 'ts-mockito'; import { ChatHandler } from '../src/chat-handler'; import { ChatConfig } from '../src/chat-config'; import { User } from '../src/user'; import { Logger, setLogger } from '../src/logging/logger'; import Response, { ResponseCommand } from '../src/response'; import { UserEvent } from '../src/user-event'; import { UserEventType } from '../src/user-event-type'; import { Abilities } from '../src/abilities'; describe('getProtocols', () => { it('returns default protocols if nothing is defined', () => { const protocols = getProtocols(); expect(protocols).not.toEqual([]); expect(protocols).toContain('jpeg'); }); it('returns included protocols if no exludes are defined', () => { const includes = ['i1', 'i2', 'i3']; const protocols = getProtocols(includes); expect(protocols).toEqual(includes); }); it('returns included protocols without the exluded', () => { const includes = ['i1', 'i2', 'i3']; const excludes = ['i2', 'i4']; const protocols = getProtocols(includes, excludes); expect(protocols).toEqual(['i1', 'i3']); }); }); describe('Chat', () => { let chat: Chat; let handlerMock: ChatHandler; let handler: ChatHandler; let loggerMock: Logger; let loggerInst: Logger; beforeEach(async () => { loggerMock = mock(); loggerInst = instance(loggerMock); setLogger(loggerInst); const chatConfig: ChatConfig = { clientId: 'test-client-id', host: 'test-host', version: 'unit-test', }; handlerMock = mock(); handler = instance(handlerMock); chat = new Chat(chatConfig, handler); }); describe('parseCurrentUsers', () => { it('called with undefined returns undefined and logs nothing', () => { const result = chat['parseCurrentUsers'](); expect(result).toBeUndefined(); }); it('called with an invalid json returns undefined and logs error', () => { const value = 'No, this is no json'; const result = chat['parseCurrentUsers'](value); expect(result).toBeUndefined(); verify(loggerMock.error(anything(), anything())).once(); }); it('called with a json array not containing valid items returns undefined and logs error', () => { const value = '[{"userName":"TESTACCOUNT_vxtechnik"}, {"timestamp":123444}]'; const result = chat['parseCurrentUsers'](value); expect(result).toBeUndefined(); verify(loggerMock.error(anything())).once(); }); it('called with valid items returns expected users', () => { const value = '[{"userName":"TESTACCOUNT_vxtechnik"}]'; const expected: User[] = [ { userName: 'TESTACCOUNT_vxtechnik', }, ]; const result = chat['parseCurrentUsers'](value); expect(result).toMatchObject(expected); }); }); describe('isInVoyeurMode', () => { describe('after init', () => { const initResponse = new Response(2000); it('is true if init returns a chat type of "VOYEUR"', () => { initResponse.values['chattype'] = 'VOYEUR'; chat['_processInitCommandResponse'](initResponse); expect(chat.isInVoyeurMode).toBe(true); }); it('is true if init returns returns any other chat type', () => { initResponse.values['chattype'] = 'VIDEO'; chat['_processInitCommandResponse'](initResponse); expect(chat.isInVoyeurMode).toBe(false); }); }); it('is set to false it texting is enabled', () => { chat['_isInVoyeurMode'] = true; chat['_processStartTextCommand'](); expect(chat.isInVoyeurMode).toBe(false); }); it('calls the handler if its value changes', () => { chat['_isInVoyeurMode'] = true; chat['_processStartTextCommand'](); verify(handlerMock.onLeftVoyeurMode()).once(); }); }); describe('_processPublicChatCommand', () => { beforeEach(() => { chat['currentUsers'] = []; }); it('with command with invalid values does nothing but logging a warning', () => { const cmd: ResponseCommand = { command: 'PUBLIC_CHAT', id: '', values: { wrongOne: 1, }, }; chat['_processPublicChatCommand'](cmd); verify(handlerMock.onUserEvent(anything())).never(); verify(loggerMock.warn(anything(), anything())).once(); }); it('with command with invalid event does nothing but logging a warning', () => { const cmd: ResponseCommand = { command: 'PUBLIC_CHAT', id: '', values: { user: '{"userName":"test-user"}', event: 'unknown', }, }; chat['_processPublicChatCommand'](cmd); verify(handlerMock.onUserEvent(anything())).never(); verify(loggerMock.warn(anything(), anything())).once(); }); it('with command with a user left event does not call when user is not present', () => { const cmd: ResponseCommand = { command: 'PUBLIC_CHAT', id: '', values: { user: '{"userName":"test-user"}', event: 'left', }, }; chat['_processPublicChatCommand'](cmd); expect(chat.currentUsers).toMatchObject([]); verify(handlerMock.onUserEvent(anything())).never(); }); it('with command with a user left event does call when user is present', () => { const expectedUser: User = { userName: 'test-user', }; const expectedEvent = new UserEvent(expectedUser, UserEventType.Left); chat['currentUsers'] = [expectedUser]; const cmd: ResponseCommand = { command: 'PUBLIC_CHAT', id: '', values: { user: '{"userName":"test-user"}', event: 'left', }, }; chat['_processPublicChatCommand'](cmd); expect(chat.currentUsers).toMatchObject([]); const [receivedEvent] = capture(handlerMock.onUserEvent).last(); expect(receivedEvent).toMatchObject(expectedEvent); }); it('with command with a user entered event calls the onUserEvent handler', () => { const cmd: ResponseCommand = { command: 'PUBLIC_CHAT', id: '', values: { user: '{"userName":"test-user"}', event: 'entered', }, }; const expectedUser: User = { userName: 'test-user', }; const expectedEvent = new UserEvent(expectedUser, UserEventType.Entered); chat['_processPublicChatCommand'](cmd); expect(chat.currentUsers).toMatchObject([expectedUser]); const [receivedEvent] = capture(handlerMock.onUserEvent).last(); expect(receivedEvent).toMatchObject(expectedEvent); }); }); describe('_processCommand', () => { it('with group chat message calls onMessage with expected parameters', () => { const expectedText = ' message from another user'; const expectedFrom = 'andrei'; const cmd = { command: 'CMSG', id: undefined, values: { messageKey: '23a7d4c627454fcfa033a2314895a497', style: 'g', sound: 'text', msgType: 'chat', from: expectedFrom, text: expectedText, }, }; resetCalls(handlerMock); chat['_processCommand'](cmd); verify( handlerMock.onMessage(expectedText, expectedFrom, anything(), anything()) ).once(); }); }); describe('_processUpdateCommand', () => { beforeEach(() => { chat.abilities = { audio: false, preview: false, single: false, text: false, tip: false, upstream: false, video: false, private: false, cam2cam: false, }; }); describe('with ability', () => { const abilityCases = [ ['private', 'canSingle', '1', '0'], ['single', 'canSingle', '1', '0'], ['text', 'canText', '1', '0'], ['cam2cam', 'canCam2Cam', '1', '0'], ['upstream', 'canCam2Cam', '1', '0'], ]; describe.each(abilityCases)( '%s', (ability: string, valueName: string, trueValue: string, falseValue: string) => { const setTrueCommand = { command: '', id: '', values: {}, }; const setFalseCommand = { command: '', id: '', values: {}, }; beforeAll(() => { setTrueCommand.values[valueName] = trueValue; setFalseCommand.values[valueName] = falseValue; }); it(`sets to true if ${valueName}=${trueValue} is received`, () => { chat['_processUpdateCommand'](setTrueCommand); expect(chat.abilities[ability]).toBe(true); }); it(`sets to false if ${valueName}=${trueValue} is received`, () => { chat['_processUpdateCommand'](setFalseCommand); expect(chat.abilities[ability]).toBe(false); }); it('calls if switched from false to true', () => { chat['_processUpdateCommand'](setFalseCommand); resetCalls(handlerMock); chat['_processUpdateCommand'](setTrueCommand); verify( handlerMock.onAbilityUpdate(ability as keyof Abilities, true) ).once(); }); it('does not call if resetted', () => { chat['_processUpdateCommand'](setTrueCommand); resetCalls(handlerMock); chat['_processUpdateCommand'](setTrueCommand); verify( handlerMock.onAbilityUpdate(ability as keyof Abilities, true) ).never(); }); it('calls if switched from true to false', () => { chat['_processUpdateCommand'](setTrueCommand); resetCalls(handlerMock); chat['_processUpdateCommand'](setFalseCommand); verify( handlerMock.onAbilityUpdate(ability as keyof Abilities, false) ).once(); }); } ); }); describe('isSingle', () => { const setTrueCommand = { command: '', id: '', values: { isSingle: '1', }, }; const setFalseCommand = { command: '', id: '', values: { isSingle: '0', }, }; it('sets to true if isSingle="1" is received', () => { chat['_processUpdateCommand'](setTrueCommand); expect(chat.singleMode).toBe(true); }); it('sets to false if isSingle="0" is received', () => { chat['_processUpdateCommand'](setFalseCommand); expect(chat.singleMode).toBe(false); }); it('calls onSingleModeUpdate if switched from false to true', () => { chat['_processUpdateCommand'](setFalseCommand); resetCalls(handlerMock); chat['_processUpdateCommand'](setTrueCommand); verify(handlerMock.onSingleModeUpdate(true)).once(); }); it('does not call onSingleModeUpdate if resetted', () => { chat['_processUpdateCommand'](setTrueCommand); resetCalls(handlerMock); chat['_processUpdateCommand'](setTrueCommand); verify(handlerMock.onSingleModeUpdate(true)).never(); }); it('calls onSingleModeUpdate if switched from true to false', () => { chat['_processUpdateCommand'](setTrueCommand); resetCalls(handlerMock); chat['_processUpdateCommand'](setFalseCommand); verify(handlerMock.onSingleModeUpdate(false)).once(); }); }); describe('audioMuted', () => { const setTrueCommand = { command: '', id: '', values: { audioMuted: '1', }, }; const setFalseCommand = { command: '', id: '', values: { audioMuted: '0', }, }; it('sets to true if audioMuted="1" is received', () => { chat['_processUpdateCommand'](setTrueCommand); expect(chat.audioMuted).toBe(true); }); it('sets to false if audioMuted="0" is received', () => { chat['_processUpdateCommand'](setFalseCommand); expect(chat.audioMuted).toBe(false); }); it('calls onAudioMuteUpdate if switched from false to true', () => { chat['_processUpdateCommand'](setFalseCommand); resetCalls(handlerMock); chat['_processUpdateCommand'](setTrueCommand); verify(handlerMock.onAudioMuteUpdate(true)).once(); }); it('does not call onAudioMuteUpdate if resetted', () => { chat['_processUpdateCommand'](setTrueCommand); resetCalls(handlerMock); chat['_processUpdateCommand'](setTrueCommand); verify(handlerMock.onAudioMuteUpdate(true)).never(); }); it('calls onAudioMuteUpdate if switched from true to false', () => { chat['_processUpdateCommand'](setTrueCommand); resetCalls(handlerMock); chat['_processUpdateCommand'](setFalseCommand); verify(handlerMock.onAudioMuteUpdate(false)).once(); }); }); describe('videolimit_rest', () => { const belowCommand = { command: '', id: '', values: { videolimit_rest: '8', below_threshold: '1', }, }; const aboveCommand = { command: '', id: '', values: { videolimit_rest: '12', below_threshold: '0', }, }; it('calls the onVideoLimitUpdate callback when below the threshold', () => { chat['_processUpdateCommand'](belowCommand); verify(handlerMock.onVideoLimitWarningUpdate(true, 8000)).once(); }); it('calls the onVideoLimitUpdate callback when above the threshold', () => { chat['_processUpdateCommand'](aboveCommand); verify(handlerMock.onVideoLimitWarningUpdate(false, 12000)).once(); }); }); }); describe('_processInitCommandResponse', () => { it('returns the defined user image when an imgSrc is provided', () => { const expectedImageSrc = 'HOST_IMAGE_URL'; const response = new Response(2000); response.values['imgSrc'] = expectedImageSrc; chat['_processInitCommandResponse'](response); expect(chat.host.imageSrc).toBe(expectedImageSrc); }); }); });