/* * SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file) * * SPDX-License-Identifier: AGPL-3.0-only */ import { Message, MessageTransporter, MessageType } from '@hedgedoc/commons'; import { Mock } from 'ts-mockery'; import { Note } from '../../notes/note.entity'; import { RealtimeConnection } from './realtime-connection'; import { RealtimeNote } from './realtime-note'; import { MockConnectionBuilder } from './test-utils/mock-connection'; type SendMessageSpy = jest.SpyInstance< void, [Required<MessageTransporter['sendMessage']>] >; describe('realtime user status adapter', () => { let client1: RealtimeConnection; let client2: RealtimeConnection; let client3: RealtimeConnection; let client4: RealtimeConnection; let sendMessage1Spy: SendMessageSpy; let sendMessage2Spy: SendMessageSpy; let sendMessage3Spy: SendMessageSpy; let sendMessage4Spy: SendMessageSpy; let realtimeNote: RealtimeNote; const username1 = 'mock1'; const username2 = 'mock2'; const username3 = 'mock3'; const username4 = 'mock4'; beforeEach(() => { realtimeNote = new RealtimeNote( Mock.of<Note>({ id: 9876 }), 'mockedContent', ); client1 = new MockConnectionBuilder(realtimeNote) .withRealtimeUserState() .withUsername(username1) .build(); client2 = new MockConnectionBuilder(realtimeNote) .withRealtimeUserState() .withUsername(username2) .build(); client3 = new MockConnectionBuilder(realtimeNote) .withRealtimeUserState() .withUsername(username3) .build(); client4 = new MockConnectionBuilder(realtimeNote) .withRealtimeUserState() .withUsername(username4) .build(); sendMessage1Spy = jest.spyOn(client1.getTransporter(), 'sendMessage'); sendMessage2Spy = jest.spyOn(client2.getTransporter(), 'sendMessage'); sendMessage3Spy = jest.spyOn(client3.getTransporter(), 'sendMessage'); sendMessage4Spy = jest.spyOn(client4.getTransporter(), 'sendMessage'); client1.getTransporter().sendReady(); client2.getTransporter().sendReady(); client3.getTransporter().sendReady(); //client 4 shouldn't be ready on purpose }); it('can answer a state request', () => { expect(sendMessage1Spy).toHaveBeenCalledTimes(0); expect(sendMessage2Spy).toHaveBeenCalledTimes(0); expect(sendMessage3Spy).toHaveBeenCalledTimes(0); expect(sendMessage4Spy).toHaveBeenCalledTimes(0); client1.getTransporter().emit(MessageType.REALTIME_USER_STATE_REQUEST); const expectedMessage1: Message<MessageType.REALTIME_USER_STATE_SET> = { type: MessageType.REALTIME_USER_STATE_SET, payload: [ { active: true, cursor: { from: 0, to: 0, }, styleIndex: 1, username: username2, displayName: username2, }, { active: true, cursor: { from: 0, to: 0, }, styleIndex: 2, username: username3, displayName: username3, }, ], }; expect(sendMessage1Spy).toHaveBeenNthCalledWith(1, expectedMessage1); expect(sendMessage2Spy).toHaveBeenCalledTimes(0); expect(sendMessage3Spy).toHaveBeenCalledTimes(0); expect(sendMessage4Spy).toHaveBeenCalledTimes(0); }); it('can save an cursor update', () => { expect(sendMessage1Spy).toHaveBeenCalledTimes(0); expect(sendMessage2Spy).toHaveBeenCalledTimes(0); expect(sendMessage3Spy).toHaveBeenCalledTimes(0); expect(sendMessage4Spy).toHaveBeenCalledTimes(0); const newFrom = Math.floor(Math.random() * 100); const newTo = Math.floor(Math.random() * 100); client1.getTransporter().emit(MessageType.REALTIME_USER_SINGLE_UPDATE, { type: MessageType.REALTIME_USER_SINGLE_UPDATE, payload: { from: newFrom, to: newTo, }, }); const expectedMessage2: Message<MessageType.REALTIME_USER_STATE_SET> = { type: MessageType.REALTIME_USER_STATE_SET, payload: [ { active: true, cursor: { from: newFrom, to: newTo, }, styleIndex: 0, username: username1, displayName: username1, }, { active: true, cursor: { from: 0, to: 0, }, styleIndex: 2, username: username3, displayName: username3, }, ], }; const expectedMessage3: Message<MessageType.REALTIME_USER_STATE_SET> = { type: MessageType.REALTIME_USER_STATE_SET, payload: [ { active: true, cursor: { from: newFrom, to: newTo, }, styleIndex: 0, username: username1, displayName: username1, }, { active: true, cursor: { from: 0, to: 0, }, styleIndex: 1, username: username2, displayName: username2, }, ], }; expect(sendMessage1Spy).toHaveBeenCalledTimes(0); expect(sendMessage2Spy).toHaveBeenNthCalledWith(1, expectedMessage2); expect(sendMessage3Spy).toHaveBeenNthCalledWith(1, expectedMessage3); expect(sendMessage4Spy).toHaveBeenCalledTimes(0); }); it('will inform other clients about removed client', () => { expect(sendMessage1Spy).toHaveBeenCalledTimes(0); expect(sendMessage2Spy).toHaveBeenCalledTimes(0); expect(sendMessage3Spy).toHaveBeenCalledTimes(0); expect(sendMessage4Spy).toHaveBeenCalledTimes(0); client2.getTransporter().disconnect(); const expectedMessage1: Message<MessageType.REALTIME_USER_STATE_SET> = { type: MessageType.REALTIME_USER_STATE_SET, payload: [ { active: true, cursor: { from: 0, to: 0, }, styleIndex: 2, username: username3, displayName: username3, }, ], }; const expectedMessage3: Message<MessageType.REALTIME_USER_STATE_SET> = { type: MessageType.REALTIME_USER_STATE_SET, payload: [ { active: true, cursor: { from: 0, to: 0, }, styleIndex: 0, username: username1, displayName: username1, }, ], }; expect(sendMessage1Spy).toHaveBeenNthCalledWith(1, expectedMessage1); expect(sendMessage2Spy).toHaveBeenCalledTimes(0); expect(sendMessage3Spy).toHaveBeenNthCalledWith(1, expectedMessage3); expect(sendMessage4Spy).toHaveBeenCalledTimes(0); }); });