From dc7a990d3114ebf93945f4c14c5de12da39b08bc Mon Sep 17 00:00:00 2001 From: Zita Szupera Date: Fri, 15 Sep 2023 13:20:31 +0200 Subject: [PATCH] feat: emit message in messageUpdate output --- .../src/lib/channel.service.spec.ts | 9 ++- .../src/lib/channel.service.thread.spec.ts | 2 +- .../src/lib/channel.service.ts | 65 +++++++++++++------ .../message-input.component.spec.ts | 5 +- .../message-input/message-input.component.ts | 8 ++- .../src/lib/mocks/index.ts | 44 ++++++++----- 6 files changed, 91 insertions(+), 42 deletions(-) diff --git a/projects/stream-chat-angular/src/lib/channel.service.spec.ts b/projects/stream-chat-angular/src/lib/channel.service.spec.ts index 961523fd..c93617e5 100644 --- a/projects/stream-chat-angular/src/lib/channel.service.spec.ts +++ b/projects/stream-chat-angular/src/lib/channel.service.spec.ts @@ -1256,7 +1256,14 @@ describe('ChannelService', () => { true ); - expect(latestMessage.status).toBe('sending'); + expect(channel.state.addMessageSorted).toHaveBeenCalledWith( + jasmine.objectContaining({ + status: 'received', + }), + true + ); + + expect(latestMessage.status).toBe('received'); }); it('should set message state while sending', async () => { diff --git a/projects/stream-chat-angular/src/lib/channel.service.thread.spec.ts b/projects/stream-chat-angular/src/lib/channel.service.thread.spec.ts index 48fa90e9..149dce4e 100644 --- a/projects/stream-chat-angular/src/lib/channel.service.thread.spec.ts +++ b/projects/stream-chat-angular/src/lib/channel.service.thread.spec.ts @@ -548,7 +548,7 @@ describe('ChannelService - threads', () => { service.activeChannel$.pipe(first()).subscribe((c) => (channel = c!)); const parentMessage = mockMessage(); parentMessage.id = 'parentId'; - const replies = [mockMessage(), mockMessage()]; + const replies = [mockMessage(1), mockMessage(2)]; replies.forEach((r) => (r.parent_id = parentMessage.id)); channel.state.threads[parentMessage.id] = replies; spyOn(channel, 'getReplies').and.resolveTo({ diff --git a/projects/stream-chat-angular/src/lib/channel.service.ts b/projects/stream-chat-angular/src/lib/channel.service.ts index e07a2b7a..2f287f4a 100644 --- a/projects/stream-chat-angular/src/lib/channel.service.ts +++ b/projects/stream-chat-angular/src/lib/channel.service.ts @@ -6,7 +6,7 @@ import { ReplaySubject, Subscription, } from 'rxjs'; -import { first, map, shareReplay } from 'rxjs/operators'; +import { first, map, shareReplay, take } from 'rxjs/operators'; import { Attachment, Channel, @@ -677,7 +677,8 @@ export class ChannelService< const channel = this.activeChannelSubject.getValue()!; preview.readBy = []; channel.state.addMessageSorted(preview, true); - await this.sendMessageRequest(preview, customData); + const response = await this.sendMessageRequest(preview, customData); + return response; } /** @@ -704,9 +705,15 @@ export class ChannelService< async updateMessage(message: StreamMessage) { const messageToUpdate = { ...message }; delete messageToUpdate.i18n; - await this.chatClientService.chatClient.updateMessage( + const response = await this.chatClientService.chatClient.updateMessage( messageToUpdate as any as UpdatedMessage ); + + const channel = this.channelsSubject + .getValue() + ?.find((c) => c.cid === message.cid); + + return this.transformToStreamMessage(response.message, channel); } /** @@ -879,20 +886,24 @@ export class ChannelService< quoted_message_id: preview.quoted_message_id, ...customData, } as Message); // TODO: find out why we need typecast here - if (response?.message) { - channel.state.addMessageSorted( - { - ...response.message, - status: 'received', - }, - true - ); - isThreadReply - ? this.activeThreadMessagesSubject.next([ - ...channel.state.threads[preview.parent_id!], - ]) - : this.activeChannelMessagesSubject.next([...channel.state.messages]); - } + channel.state.addMessageSorted( + { + ...response.message, + status: 'received', + }, + true + ); + isThreadReply + ? this.activeThreadMessagesSubject.next([ + ...channel.state.threads[preview.parent_id!], + ]) + : this.activeChannelMessagesSubject.next([...channel.state.messages]); + let messages!: StreamMessage[]; + (isThreadReply ? this.activeThreadMessages$ : this.activeChannelMessages$) + .pipe(take(1)) + .subscribe((m) => (messages = m)); + const newMessage = messages[messages.length - 1]!; + return newMessage; } catch (error) { const stringError = JSON.stringify(error); const parsedError: { status?: number } = stringError @@ -912,6 +923,12 @@ export class ChannelService< ...channel.state.threads[preview.parent_id!], ]) : this.activeChannelMessagesSubject.next([...channel.state.messages]); + let messages!: StreamMessage[]; + (isThreadReply ? this.activeThreadMessages$ : this.activeChannelMessages$) + .pipe(take(1)) + .subscribe((m) => (messages = m)); + const newMessage = messages[messages.length - 1]!; + return newMessage; } } @@ -1521,7 +1538,7 @@ export class ChannelService< private transformToStreamMessage( message: StreamMessage | MessageResponse | FormatMessageResponse, - channel: Channel + channel?: Channel ) { const isThreadMessage = !!message.parent_id; if ( @@ -1555,7 +1572,11 @@ export class ChannelService< if (this.isFormatMessageResponse(message)) { return { ...message, - readBy: isThreadMessage ? [] : getReadBy(message, channel), + readBy: isThreadMessage + ? [] + : channel + ? getReadBy(message, channel) + : [], translation: getMessageTranslation( message, channel, @@ -1566,7 +1587,11 @@ export class ChannelService< const formatMessage = this.formatMessage(message); return { ...formatMessage, - readBy: isThreadMessage ? [] : getReadBy(formatMessage, channel), + readBy: isThreadMessage + ? [] + : channel + ? getReadBy(formatMessage, channel) + : [], translation: getMessageTranslation( message, channel, diff --git a/projects/stream-chat-angular/src/lib/message-input/message-input.component.spec.ts b/projects/stream-chat-angular/src/lib/message-input/message-input.component.spec.ts index e40b15ba..17ab9836 100644 --- a/projects/stream-chat-angular/src/lib/message-input/message-input.component.spec.ts +++ b/projects/stream-chat-angular/src/lib/message-input/message-input.component.spec.ts @@ -238,9 +238,12 @@ describe('MessageInputComponent', () => { fixture.detectChanges(); const spy = jasmine.createSpy(); component.messageUpdate.subscribe(spy); + updateMessageSpy.and.resolveTo({ id: component.message.id }); await component.messageSent(); - expect(spy).toHaveBeenCalledWith(undefined); + expect(spy).toHaveBeenCalledWith({ + message: jasmine.objectContaining({ id: component.message.id }), + }); }); it('should send message if button is clicked', () => { diff --git a/projects/stream-chat-angular/src/lib/message-input/message-input.component.ts b/projects/stream-chat-angular/src/lib/message-input/message-input.component.ts index e0d03864..2cdb23d2 100644 --- a/projects/stream-chat-angular/src/lib/message-input/message-input.component.ts +++ b/projects/stream-chat-angular/src/lib/message-input/message-input.component.ts @@ -90,7 +90,9 @@ export class MessageInputComponent /** * Emits when a message was successfuly sent or updated */ - @Output() readonly messageUpdate = new EventEmitter(); + @Output() readonly messageUpdate = new EventEmitter<{ + message: StreamMessage; + }>(); @HostBinding() class = 'str-chat__message-input-angular-host'; isFileUploadAuthorized: boolean | undefined; canSendLinks: boolean | undefined; @@ -353,7 +355,7 @@ export class MessageInputComponent this.textareaValue = ''; } try { - await (this.isUpdate + const message = await (this.isUpdate ? this.channelService.updateMessage({ ...this.message!, text: text, @@ -366,7 +368,7 @@ export class MessageInputComponent this.parentMessageId, this.quotedMessage?.id )); - this.messageUpdate.emit(); + this.messageUpdate.emit({ message }); if (!this.isUpdate) { this.attachmentService.resetAttachmentUploads(); } diff --git a/projects/stream-chat-angular/src/lib/mocks/index.ts b/projects/stream-chat-angular/src/lib/mocks/index.ts index b2790176..46bac63b 100644 --- a/projects/stream-chat-angular/src/lib/mocks/index.ts +++ b/projects/stream-chat-angular/src/lib/mocks/index.ts @@ -21,9 +21,9 @@ export const mockCurrentUser = () => image: 'link/to/photo', } as UserResponse); -export const mockMessage = () => +export const mockMessage = (id?: number) => ({ - id: 'id', + id: id === undefined ? 'id' : `id${id}`, text: 'Hello from Angular SDK', user: mockCurrentUser(), type: 'regular', @@ -84,7 +84,10 @@ export const generateMockChannels = (length = 25) => { }, watch: () => {}, stopWatching: () => {}, - sendMessage: () => {}, + sendMessage: (m: any) => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + return Promise.resolve({ message: m }); + }, sendImage: () => {}, sendFile: () => {}, sendAction: () => {}, @@ -113,22 +116,31 @@ export const generateMockChannels = (length = 25) => { eddie: { user: { id: 'eddie' } }, }, addMessageSorted: function (response: MessageResponse) { - if (response.parent_id) { - if ( - (this.threads as { [key: string]: StreamMessage[] })[ - response.parent_id - ] - ) { - (this.threads as { [key: string]: StreamMessage[] })[ - response.parent_id - ].push(response as any as StreamMessage); + if (!response) { + return; + } + let array: StreamMessage[]; + const message = response as any as StreamMessage; + const threads = this.threads as { [key: string]: StreamMessage[] }; + if (message.parent_id) { + if (threads[message.parent_id]) { + array = threads[message.parent_id]; } else { - (this.threads as { [key: string]: StreamMessage[] })[ - response.parent_id - ] = [response as any as StreamMessage]; + array = []; + threads[message.parent_id] = array; } } else { - this.messages.push(response as any as StreamMessage); + array = this.messages; + } + const existingMessageIndex = array.findIndex((m) => + message.id + ? m.id === message.id + : m.created_at === message.created_at + ); + if (existingMessageIndex === -1) { + array.push(message); + } else { + array[existingMessageIndex] = message; } }, removeMessage: () => {},