refactor(realtime): solve circle dependencies in realtime-user-status-adapter.ts

Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
This commit is contained in:
Tilman Vatteroth 2023-05-14 13:16:39 +02:00
parent 8cd93918e2
commit e3a3690b58
5 changed files with 259 additions and 191 deletions

View file

@ -16,7 +16,10 @@ import { User } from '../../users/user.entity';
import * as NameRandomizerModule from './random-word-lists/name-randomizer'; import * as NameRandomizerModule from './random-word-lists/name-randomizer';
import { RealtimeConnection } from './realtime-connection'; import { RealtimeConnection } from './realtime-connection';
import { RealtimeNote } from './realtime-note'; import { RealtimeNote } from './realtime-note';
import { RealtimeUserStatusAdapter } from './realtime-user-status-adapter'; import {
OtherAdapterCollector,
RealtimeUserStatusAdapter,
} from './realtime-user-status-adapter';
import * as RealtimeUserStatusModule from './realtime-user-status-adapter'; import * as RealtimeUserStatusModule from './realtime-user-status-adapter';
jest.mock('./random-word-lists/name-randomizer'); jest.mock('./random-word-lists/name-randomizer');
@ -77,18 +80,46 @@ describe('websocket connection', () => {
it.each([true, false])( it.each([true, false])(
'returns the correct realtime user status with acceptEdits %s', 'returns the correct realtime user status with acceptEdits %s',
(acceptEdits) => { (acceptEdits) => {
const realtimeUserStatus = Mock.of<RealtimeUserStatusAdapter>(); const realtimeUserStatus1 = Mock.of<RealtimeUserStatusAdapter>();
let usedConnection: RealtimeConnection | undefined = undefined; const realtimeUserStatus2 = Mock.of<RealtimeUserStatusAdapter>();
const realtimeUserStatus3 = Mock.of<RealtimeUserStatusAdapter>();
const realtimeConnections = [
Mock.of<RealtimeConnection>({
getRealtimeUserStateAdapter: () => realtimeUserStatus1,
}),
Mock.of<RealtimeConnection>({
getRealtimeUserStateAdapter: () => realtimeUserStatus2,
}),
Mock.of<RealtimeConnection>({
getRealtimeUserStateAdapter: () => realtimeUserStatus3,
}),
];
jest jest
.spyOn(mockedRealtimeNote, 'getConnections')
.mockImplementation(() => realtimeConnections);
const constructor = jest
.spyOn(RealtimeUserStatusModule, 'RealtimeUserStatusAdapter') .spyOn(RealtimeUserStatusModule, 'RealtimeUserStatusAdapter')
.mockImplementation( .mockImplementation(
(username, displayName, connection, acceptCursorUpdateProvider) => { (
username,
displayName,
otherAdapterCollector: OtherAdapterCollector,
messageTransporter,
acceptCursorUpdateProvider,
) => {
expect(username).toBe(mockedUserName); expect(username).toBe(mockedUserName);
expect(displayName).toBe(mockedDisplayName); expect(displayName).toBe(mockedDisplayName);
usedConnection = connection; expect(otherAdapterCollector()).toStrictEqual([
realtimeUserStatus1,
realtimeUserStatus2,
realtimeUserStatus3,
]);
expect(messageTransporter).toBe(mockedMessageTransporter);
expect(acceptCursorUpdateProvider()).toBe(acceptEdits); expect(acceptCursorUpdateProvider()).toBe(acceptEdits);
return realtimeUserStatus; return realtimeUserStatus1;
}, },
); );
@ -99,8 +130,14 @@ describe('websocket connection', () => {
acceptEdits, acceptEdits,
); );
expect(usedConnection).toBe(sut); expect(constructor).toHaveBeenCalledWith(
expect(sut.getRealtimeUserStateAdapter()).toBe(realtimeUserStatus); mockedUserName,
mockedDisplayName,
expect.anything(),
mockedMessageTransporter,
expect.anything(),
);
expect(sut.getRealtimeUserStateAdapter()).toBe(realtimeUserStatus1);
}, },
); );

View file

@ -50,7 +50,11 @@ export class RealtimeConnection {
this.realtimeUserStateAdapter = new RealtimeUserStatusAdapter( this.realtimeUserStateAdapter = new RealtimeUserStatusAdapter(
this.user?.username ?? null, this.user?.username ?? null,
this.getDisplayName(), this.getDisplayName(),
this, () =>
this.realtimeNote
.getConnections()
.map((connection) => connection.getRealtimeUserStateAdapter()),
this.getTransporter(),
() => acceptEdits, () => acceptEdits,
); );
} }

View file

@ -3,25 +3,25 @@
* *
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
import { Message, MessageTransporter, MessageType } from '@hedgedoc/commons'; import {
import { Mock } from 'ts-mockery'; Message,
MessageType,
MockedBackendMessageTransporter,
} from '@hedgedoc/commons';
import { Note } from '../../notes/note.entity'; import { RealtimeUserStatusAdapter } from './realtime-user-status-adapter';
import { RealtimeConnection } from './realtime-connection';
import { RealtimeNote } from './realtime-note';
import { MockConnectionBuilder } from './test-utils/mock-connection';
type SendMessageSpy = jest.SpyInstance< type SendMessageSpy = jest.SpyInstance<
void, void,
[Required<MessageTransporter['sendMessage']>] [Required<MockedBackendMessageTransporter['sendMessage']>]
>; >;
describe('realtime user status adapter', () => { describe('realtime user status adapter', () => {
let clientLoggedIn1: RealtimeConnection; let clientLoggedIn1: RealtimeUserStatusAdapter | undefined;
let clientLoggedIn2: RealtimeConnection; let clientLoggedIn2: RealtimeUserStatusAdapter | undefined;
let clientGuest: RealtimeConnection; let clientGuest: RealtimeUserStatusAdapter | undefined;
let clientNotReady: RealtimeConnection; let clientNotReady: RealtimeUserStatusAdapter | undefined;
let clientDecline: RealtimeConnection; let clientDecline: RealtimeUserStatusAdapter | undefined;
let clientLoggedIn1SendMessageSpy: SendMessageSpy; let clientLoggedIn1SendMessageSpy: SendMessageSpy;
let clientLoggedIn2SendMessageSpy: SendMessageSpy; let clientLoggedIn2SendMessageSpy: SendMessageSpy;
@ -29,8 +29,6 @@ describe('realtime user status adapter', () => {
let clientNotReadySendMessageSpy: SendMessageSpy; let clientNotReadySendMessageSpy: SendMessageSpy;
let clientDeclineSendMessageSpy: SendMessageSpy; let clientDeclineSendMessageSpy: SendMessageSpy;
let realtimeNote: RealtimeNote;
const clientLoggedIn1Username = 'logged.in1'; const clientLoggedIn1Username = 'logged.in1';
const clientLoggedIn2Username = 'logged.in2'; const clientLoggedIn2Username = 'logged.in2';
const clientNotReadyUsername = 'not.ready'; const clientNotReadyUsername = 'not.ready';
@ -38,46 +36,96 @@ describe('realtime user status adapter', () => {
const guestDisplayName = 'Virtuous Mockingbird'; const guestDisplayName = 'Virtuous Mockingbird';
function spyOnSendMessage(connection: RealtimeConnection): jest.SpyInstance { let messageTransporterLoggedIn1: MockedBackendMessageTransporter;
return jest.spyOn(connection.getTransporter(), 'sendMessage'); let messageTransporterLoggedIn2: MockedBackendMessageTransporter;
} let messageTransporterGuest: MockedBackendMessageTransporter;
let messageTransporterNotReady: MockedBackendMessageTransporter;
let messageTransporterDecline: MockedBackendMessageTransporter;
beforeEach(() => { beforeEach(() => {
realtimeNote = new RealtimeNote( clientLoggedIn1 = undefined;
Mock.of<Note>({ id: 9876 }), clientLoggedIn2 = undefined;
'mockedContent', clientGuest = undefined;
clientNotReady = undefined;
clientDecline = undefined;
messageTransporterLoggedIn1 = new MockedBackendMessageTransporter('');
messageTransporterLoggedIn2 = new MockedBackendMessageTransporter('');
messageTransporterGuest = new MockedBackendMessageTransporter('');
messageTransporterNotReady = new MockedBackendMessageTransporter('');
messageTransporterDecline = new MockedBackendMessageTransporter('');
function otherAdapterCollector(): RealtimeUserStatusAdapter[] {
return [
clientLoggedIn1,
clientLoggedIn2,
clientGuest,
clientNotReady,
clientDecline,
].filter((value) => value !== undefined) as RealtimeUserStatusAdapter[];
}
clientLoggedIn1 = new RealtimeUserStatusAdapter(
clientLoggedIn1Username,
clientLoggedIn1Username,
otherAdapterCollector,
messageTransporterLoggedIn1,
() => true,
);
clientLoggedIn2 = new RealtimeUserStatusAdapter(
clientLoggedIn2Username,
clientLoggedIn2Username,
otherAdapterCollector,
messageTransporterLoggedIn2,
() => true,
);
clientGuest = new RealtimeUserStatusAdapter(
null,
guestDisplayName,
otherAdapterCollector,
messageTransporterGuest,
() => true,
);
clientNotReady = new RealtimeUserStatusAdapter(
clientNotReadyUsername,
clientNotReadyUsername,
otherAdapterCollector,
messageTransporterNotReady,
() => true,
);
clientDecline = new RealtimeUserStatusAdapter(
clientDeclineUsername,
clientDeclineUsername,
otherAdapterCollector,
messageTransporterDecline,
() => false,
); );
clientLoggedIn1 = new MockConnectionBuilder(realtimeNote)
.withAcceptingRealtimeUserStatus()
.withLoggedInUser(clientLoggedIn1Username)
.build();
clientLoggedIn2 = new MockConnectionBuilder(realtimeNote)
.withAcceptingRealtimeUserStatus()
.withLoggedInUser(clientLoggedIn2Username)
.build();
clientGuest = new MockConnectionBuilder(realtimeNote)
.withAcceptingRealtimeUserStatus()
.withGuestUser(guestDisplayName)
.build();
clientNotReady = new MockConnectionBuilder(realtimeNote)
.withAcceptingRealtimeUserStatus()
.withLoggedInUser(clientNotReadyUsername)
.build();
clientDecline = new MockConnectionBuilder(realtimeNote)
.withDecliningRealtimeUserStatus()
.withLoggedInUser(clientDeclineUsername)
.build();
clientLoggedIn1SendMessageSpy = spyOnSendMessage(clientLoggedIn1); clientLoggedIn1SendMessageSpy = jest.spyOn(
clientLoggedIn2SendMessageSpy = spyOnSendMessage(clientLoggedIn2); messageTransporterLoggedIn1,
clientGuestSendMessageSpy = spyOnSendMessage(clientGuest); 'sendMessage',
clientNotReadySendMessageSpy = spyOnSendMessage(clientNotReady); );
clientDeclineSendMessageSpy = spyOnSendMessage(clientDecline); clientLoggedIn2SendMessageSpy = jest.spyOn(
messageTransporterLoggedIn2,
'sendMessage',
);
clientGuestSendMessageSpy = jest.spyOn(
messageTransporterGuest,
'sendMessage',
);
clientNotReadySendMessageSpy = jest.spyOn(
messageTransporterNotReady,
'sendMessage',
);
clientDeclineSendMessageSpy = jest.spyOn(
messageTransporterDecline,
'sendMessage',
);
clientLoggedIn1.getTransporter().sendReady(); messageTransporterLoggedIn1.sendReady();
clientLoggedIn2.getTransporter().sendReady(); messageTransporterLoggedIn2.sendReady();
clientGuest.getTransporter().sendReady(); messageTransporterGuest.sendReady();
clientDecline.getTransporter().sendReady(); messageTransporterDecline.sendReady();
}); });
it('can answer a state request', () => { it('can answer a state request', () => {
@ -87,9 +135,7 @@ describe('realtime user status adapter', () => {
expect(clientNotReadySendMessageSpy).toHaveBeenCalledTimes(0); expect(clientNotReadySendMessageSpy).toHaveBeenCalledTimes(0);
expect(clientDeclineSendMessageSpy).toHaveBeenCalledTimes(0); expect(clientDeclineSendMessageSpy).toHaveBeenCalledTimes(0);
clientLoggedIn1 messageTransporterLoggedIn1.emit(MessageType.REALTIME_USER_STATE_REQUEST);
.getTransporter()
.emit(MessageType.REALTIME_USER_STATE_REQUEST);
const expectedMessage1: Message<MessageType.REALTIME_USER_STATE_SET> = { const expectedMessage1: Message<MessageType.REALTIME_USER_STATE_SET> = {
type: MessageType.REALTIME_USER_STATE_SET, type: MessageType.REALTIME_USER_STATE_SET,
@ -149,9 +195,7 @@ describe('realtime user status adapter', () => {
const newFrom = Math.floor(Math.random() * 100); const newFrom = Math.floor(Math.random() * 100);
const newTo = Math.floor(Math.random() * 100); const newTo = Math.floor(Math.random() * 100);
clientLoggedIn1 messageTransporterLoggedIn1.emit(MessageType.REALTIME_USER_SINGLE_UPDATE, {
.getTransporter()
.emit(MessageType.REALTIME_USER_SINGLE_UPDATE, {
type: MessageType.REALTIME_USER_SINGLE_UPDATE, type: MessageType.REALTIME_USER_SINGLE_UPDATE,
payload: { payload: {
from: newFrom, from: newFrom,
@ -302,7 +346,7 @@ describe('realtime user status adapter', () => {
expect(clientNotReadySendMessageSpy).toHaveBeenCalledTimes(0); expect(clientNotReadySendMessageSpy).toHaveBeenCalledTimes(0);
expect(clientDeclineSendMessageSpy).toHaveBeenCalledTimes(0); expect(clientDeclineSendMessageSpy).toHaveBeenCalledTimes(0);
clientLoggedIn2.getTransporter().disconnect(); messageTransporterLoggedIn2.disconnect();
const expectedMessage1: Message<MessageType.REALTIME_USER_STATE_SET> = { const expectedMessage1: Message<MessageType.REALTIME_USER_STATE_SET> = {
type: MessageType.REALTIME_USER_STATE_SET, type: MessageType.REALTIME_USER_STATE_SET,
@ -417,9 +461,7 @@ describe('realtime user status adapter', () => {
expect(clientNotReadySendMessageSpy).toHaveBeenCalledTimes(0); expect(clientNotReadySendMessageSpy).toHaveBeenCalledTimes(0);
expect(clientDeclineSendMessageSpy).toHaveBeenCalledTimes(0); expect(clientDeclineSendMessageSpy).toHaveBeenCalledTimes(0);
clientLoggedIn1 messageTransporterLoggedIn1.emit(MessageType.REALTIME_USER_SET_ACTIVITY, {
.getTransporter()
.emit(MessageType.REALTIME_USER_SET_ACTIVITY, {
type: MessageType.REALTIME_USER_SET_ACTIVITY, type: MessageType.REALTIME_USER_SET_ACTIVITY,
payload: { payload: {
active: false, active: false,
@ -564,9 +606,7 @@ describe('realtime user status adapter', () => {
expectedInactivityMessage5, expectedInactivityMessage5,
); );
clientLoggedIn1 messageTransporterLoggedIn1.emit(MessageType.REALTIME_USER_SET_ACTIVITY, {
.getTransporter()
.emit(MessageType.REALTIME_USER_SET_ACTIVITY, {
type: MessageType.REALTIME_USER_SET_ACTIVITY, type: MessageType.REALTIME_USER_SET_ACTIVITY,
payload: { payload: {
active: false, active: false,
@ -588,9 +628,14 @@ describe('realtime user status adapter', () => {
expectedInactivityMessage5, expectedInactivityMessage5,
); );
clientLoggedIn1 messageTransporterLoggedIn1.emit(MessageType.REALTIME_USER_SET_ACTIVITY, {
.getTransporter() type: MessageType.REALTIME_USER_SET_ACTIVITY,
.emit(MessageType.REALTIME_USER_SET_ACTIVITY, { payload: {
active: true,
},
});
messageTransporterLoggedIn1.emit(MessageType.REALTIME_USER_SET_ACTIVITY, {
type: MessageType.REALTIME_USER_SET_ACTIVITY, type: MessageType.REALTIME_USER_SET_ACTIVITY,
payload: { payload: {
active: true, active: true,
@ -735,9 +780,7 @@ describe('realtime user status adapter', () => {
expectedReactivityMessage5, expectedReactivityMessage5,
); );
clientLoggedIn1 messageTransporterLoggedIn1.emit(MessageType.REALTIME_USER_SET_ACTIVITY, {
.getTransporter()
.emit(MessageType.REALTIME_USER_SET_ACTIVITY, {
type: MessageType.REALTIME_USER_SET_ACTIVITY, type: MessageType.REALTIME_USER_SET_ACTIVITY,
payload: { payload: {
active: true, active: true,

View file

@ -3,11 +3,15 @@
* *
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
import { Message, MessageType, RealtimeUser } from '@hedgedoc/commons'; import {
Message,
MessageTransporter,
MessageType,
RealtimeUser,
} from '@hedgedoc/commons';
import { Listener } from 'eventemitter2'; import { Listener } from 'eventemitter2';
import { RealtimeConnection } from './realtime-connection'; export type OtherAdapterCollector = () => RealtimeUserStatusAdapter[];
import { RealtimeNote } from './realtime-note';
/** /**
* Saves the current realtime status of a specific client and sends updates of changes to other clients. * Saves the current realtime status of a specific client and sends updates of changes to other clients.
@ -16,30 +20,23 @@ export class RealtimeUserStatusAdapter {
private readonly realtimeUser: RealtimeUser; private readonly realtimeUser: RealtimeUser;
constructor( constructor(
username: string | null, private readonly username: string | null,
displayName: string, private readonly displayName: string,
private connection: RealtimeConnection, private collectOtherAdapters: OtherAdapterCollector,
private messageTransporter: MessageTransporter,
private acceptCursorUpdateProvider: () => boolean, private acceptCursorUpdateProvider: () => boolean,
) { ) {
this.realtimeUser = this.createInitialRealtimeUserState( this.realtimeUser = this.createInitialRealtimeUserState();
username, this.bindRealtimeUserStateEvents();
displayName,
connection.getRealtimeNote(),
);
this.bindRealtimeUserStateEvents(connection);
} }
private createInitialRealtimeUserState( private createInitialRealtimeUserState(): RealtimeUser {
username: string | null,
displayName: string,
realtimeNote: RealtimeNote,
): RealtimeUser {
return { return {
username: username, username: this.username,
displayName: displayName, displayName: this.displayName,
active: true, active: true,
styleIndex: this.findLeastUsedStyleIndex( styleIndex: this.findLeastUsedStyleIndex(
this.createStyleIndexToCountMap(realtimeNote), this.createStyleIndexToCountMap(),
), ),
cursor: !this.acceptCursorUpdateProvider() cursor: !this.acceptCursorUpdateProvider()
? null ? null
@ -50,51 +47,55 @@ export class RealtimeUserStatusAdapter {
}; };
} }
private bindRealtimeUserStateEvents(connection: RealtimeConnection): void { private bindRealtimeUserStateEvents(): void {
const realtimeNote = connection.getRealtimeNote(); const transporterMessagesListener = this.messageTransporter.on(
const transporterMessagesListener = connection.getTransporter().on(
MessageType.REALTIME_USER_SINGLE_UPDATE, MessageType.REALTIME_USER_SINGLE_UPDATE,
(message: Message<MessageType.REALTIME_USER_SINGLE_UPDATE>) => { (message: Message<MessageType.REALTIME_USER_SINGLE_UPDATE>) => {
this.realtimeUser.cursor = this.acceptCursorUpdateProvider() this.realtimeUser.cursor = this.acceptCursorUpdateProvider()
? message.payload ? message.payload
: null; : null;
this.sendRealtimeUserStatusUpdateEvent(connection); this.collectOtherAdapters()
}, .filter((adapter) => adapter !== this)
{ objectify: true }, .forEach((adapter) => adapter.sendCompleteStateToClient());
) as Listener;
const transporterRequestMessageListener = connection.getTransporter().on(
MessageType.REALTIME_USER_STATE_REQUEST,
() => {
this.sendCompleteStateToClient(connection);
}, },
{ objectify: true }, { objectify: true },
) as Listener; ) as Listener;
const clientRemoveListener = realtimeNote.on( const transporterRequestMessageListener = this.messageTransporter.on(
'clientRemoved', MessageType.REALTIME_USER_STATE_REQUEST,
(client: RealtimeConnection) => { () => {
if (client === connection) { this.sendCompleteStateToClient();
this.sendRealtimeUserStatusUpdateEvent(connection); },
} { objectify: true },
) as Listener;
const clientRemoveListener = this.messageTransporter.on(
'disconnected',
() => {
this.collectOtherAdapters()
.filter((adapter) => adapter !== this)
.forEach((adapter) => adapter.sendCompleteStateToClient());
}, },
{ {
objectify: true, objectify: true,
}, },
) as Listener; ) as Listener;
const realtimeUserSetActivityListener = connection.getTransporter().on( const realtimeUserSetActivityListener = this.messageTransporter.on(
MessageType.REALTIME_USER_SET_ACTIVITY, MessageType.REALTIME_USER_SET_ACTIVITY,
(message: Message<MessageType.REALTIME_USER_SET_ACTIVITY>) => { (message: Message<MessageType.REALTIME_USER_SET_ACTIVITY>) => {
if (this.realtimeUser.active === message.payload.active) { if (this.realtimeUser.active === message.payload.active) {
return; return;
} }
this.realtimeUser.active = message.payload.active; this.realtimeUser.active = message.payload.active;
this.sendRealtimeUserStatusUpdateEvent(connection); this.collectOtherAdapters()
.filter((adapter) => adapter !== this)
.forEach((adapter) => adapter.sendCompleteStateToClient());
}, },
{ objectify: true }, { objectify: true },
) as Listener; ) as Listener;
connection.getTransporter().on('disconnected', () => { this.messageTransporter.on('disconnected', () => {
transporterMessagesListener?.off(); transporterMessagesListener?.off();
transporterRequestMessageListener.off(); transporterRequestMessageListener.off();
clientRemoveListener.off(); clientRemoveListener.off();
@ -102,45 +103,31 @@ export class RealtimeUserStatusAdapter {
}); });
} }
private sendRealtimeUserStatusUpdateEvent( private getSendableState(): RealtimeUser | undefined {
exceptClient: RealtimeConnection, return this.messageTransporter.isReady() ? this.realtimeUser : undefined;
): void {
this.collectAllConnectionsExcept(exceptClient).forEach(
this.sendCompleteStateToClient.bind(this),
);
} }
private sendCompleteStateToClient(receivingClient: RealtimeConnection): void { public sendCompleteStateToClient(): void {
const realtimeUser = if (!this.messageTransporter.isReady()) {
receivingClient.getRealtimeUserStateAdapter().realtimeUser; return;
const realtimeUsers = this.collectAllConnectionsExcept(receivingClient) }
.map((client) => client.getRealtimeUserStateAdapter().realtimeUser) const realtimeUsers = this.collectOtherAdapters()
.filter((realtimeUser) => realtimeUser !== null); .filter((adapter) => adapter !== this)
.map((adapter) => adapter.getSendableState())
.filter((value) => value !== undefined) as RealtimeUser[];
receivingClient.getTransporter().sendMessage({ this.messageTransporter.sendMessage({
type: MessageType.REALTIME_USER_STATE_SET, type: MessageType.REALTIME_USER_STATE_SET,
payload: { payload: {
users: realtimeUsers, users: realtimeUsers,
ownUser: { ownUser: {
displayName: realtimeUser.displayName, displayName: this.realtimeUser.displayName,
styleIndex: realtimeUser.styleIndex, styleIndex: this.realtimeUser.styleIndex,
}, },
}, },
}); });
} }
private collectAllConnectionsExcept(
exceptClient: RealtimeConnection,
): RealtimeConnection[] {
return this.connection
.getRealtimeNote()
.getConnections()
.filter(
(client) =>
client !== exceptClient && client.getTransporter().isReady(),
);
}
private findLeastUsedStyleIndex(map: Map<number, number>): number { private findLeastUsedStyleIndex(map: Map<number, number>): number {
let leastUsedStyleIndex = 0; let leastUsedStyleIndex = 0;
let leastUsedStyleIndexCount = map.get(0) ?? 0; let leastUsedStyleIndexCount = map.get(0) ?? 0;
@ -154,15 +141,9 @@ export class RealtimeUserStatusAdapter {
return leastUsedStyleIndex; return leastUsedStyleIndex;
} }
private createStyleIndexToCountMap( private createStyleIndexToCountMap(): Map<number, number> {
realtimeNote: RealtimeNote, return this.collectOtherAdapters()
): Map<number, number> { .map((adapter) => adapter.realtimeUser.styleIndex)
return realtimeNote
.getConnections()
.map(
(connection) =>
connection.getRealtimeUserStateAdapter().realtimeUser?.styleIndex,
)
.reduce((map, styleIndex) => { .reduce((map, styleIndex) => {
if (styleIndex !== undefined) { if (styleIndex !== undefined) {
const count = (map.get(styleIndex) ?? 0) + 1; const count = (map.get(styleIndex) ?? 0) + 1;

View file

@ -82,8 +82,21 @@ export class MockConnectionBuilder {
const displayName = this.deriveDisplayName(); const displayName = this.deriveDisplayName();
const transporter = new MockedBackendMessageTransporter(''); const transporter = new MockedBackendMessageTransporter('');
let realtimeUserStateAdapter: RealtimeUserStatusAdapter = const realtimeUserStateAdapter: RealtimeUserStatusAdapter =
Mock.of<RealtimeUserStatusAdapter>({}); this.includeRealtimeUserStatus === RealtimeUserState.WITHOUT
? Mock.of<RealtimeUserStatusAdapter>({})
: new RealtimeUserStatusAdapter(
this.username ?? null,
displayName,
() =>
this.realtimeNote
.getConnections()
.map((connection) => connection.getRealtimeUserStateAdapter()),
transporter,
() =>
this.includeRealtimeUserStatus ===
RealtimeUserState.WITH_READWRITE,
);
const mockUser = const mockUser =
this.username === null this.username === null
@ -107,16 +120,6 @@ export class MockConnectionBuilder {
this.realtimeNote.removeClient(connection), this.realtimeNote.removeClient(connection),
); );
if (this.includeRealtimeUserStatus !== RealtimeUserState.WITHOUT) {
realtimeUserStateAdapter = new RealtimeUserStatusAdapter(
this.username ?? null,
displayName,
connection,
() =>
this.includeRealtimeUserStatus === RealtimeUserState.WITH_READWRITE,
);
}
this.realtimeNote.addClient(connection); this.realtimeNote.addClient(connection);
return connection; return connection;